mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
Retain non-file clipboard representations while asynchronous file MIME imports are validated. If an offered URI list is invalid or cannot be read, publish the retained image and text formats instead of clearing the clipboard.
1840 lines
48 KiB
C
1840 lines
48 KiB
C
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program 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 program 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 program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "clipboard.h"
|
|
#include "x11.h"
|
|
#include "atoms.h"
|
|
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xatom.h>
|
|
|
|
#include "core/clipboard.h"
|
|
#include "core/clipboard_files.h"
|
|
#include "common/array.h"
|
|
#include "common/debug.h"
|
|
#include "common/KVMFRClipboard.h"
|
|
#include "common/locking.h"
|
|
|
|
struct X11ClipboardRead
|
|
{
|
|
Window window;
|
|
LG_ClipboardRequest request;
|
|
LG_ClipboardData type;
|
|
bool incremental;
|
|
bool begun;
|
|
bool blocked;
|
|
bool calling;
|
|
bool readyPending;
|
|
bool endPending;
|
|
bool propertyPending;
|
|
bool pendingMore;
|
|
uint64_t sizeHint;
|
|
uint64_t offset;
|
|
long propertyOffset;
|
|
unsigned long pendingUnits;
|
|
uint8_t * pending;
|
|
size_t pendingSize;
|
|
};
|
|
|
|
struct X11ClipboardWrite
|
|
{
|
|
struct X11ClipboardWrite * next;
|
|
XEvent event;
|
|
LG_ClipboardRequest request;
|
|
LG_ClipboardData type;
|
|
uint64_t offset;
|
|
bool begun;
|
|
bool blocked;
|
|
bool ready;
|
|
bool endPending;
|
|
bool creating;
|
|
bool file;
|
|
uint8_t * fileData;
|
|
size_t fileSize;
|
|
uint64_t filePresentation;
|
|
};
|
|
|
|
struct X11ClipboardFileImport
|
|
{
|
|
Window window;
|
|
Atom target;
|
|
const char * mime;
|
|
long propertyOffset;
|
|
uint8_t * data;
|
|
size_t size;
|
|
size_t capacity;
|
|
bool incremental;
|
|
LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
|
|
size_t typeCount;
|
|
};
|
|
|
|
struct X11ClipboardState
|
|
{
|
|
LG_Lock lock;
|
|
Atom aCurSelection;
|
|
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
|
Atom aFileGnome;
|
|
Atom aFileMate;
|
|
Atom aFileKde;
|
|
Window targetsWindow;
|
|
LG_ClipboardData type;
|
|
bool haveRequest;
|
|
uint64_t filePresentation;
|
|
|
|
struct X11ClipboardRead read;
|
|
struct X11ClipboardWrite * writes;
|
|
struct X11ClipboardFileImport fileImport;
|
|
};
|
|
|
|
static const char * atomTypes[] =
|
|
{
|
|
"UTF8_STRING",
|
|
"image/png",
|
|
"image/bmp",
|
|
"image/tiff",
|
|
"image/jpeg",
|
|
"text/uri-list",
|
|
};
|
|
|
|
static const char fileGnomeMime[] = "x-special/gnome-copied-files";
|
|
static const char fileMateMime [] = "x-special/mate-copied-files";
|
|
static const char fileKdeMime [] = "application/x-kde-cutselection";
|
|
static const char fileUriMime [] = "text/uri-list";
|
|
|
|
static struct X11ClipboardState x11cb;
|
|
|
|
// forwards
|
|
static void x11CBSelectionRequest(const XSelectionRequestEvent e);
|
|
static void x11CBSelectionClear(const XSelectionClearEvent e);
|
|
static void x11CBSelectionIncr(const XPropertyEvent e);
|
|
static void x11CBSelectionNotify(const XSelectionEvent e);
|
|
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e);
|
|
static LG_ClipboardRequest cancelReadNL(bool abort);
|
|
static bool writeLinkedNL(const struct X11ClipboardWrite * write);
|
|
static void writeRemoveNL(struct X11ClipboardWrite * write);
|
|
static bool advanceReadPropertyNL(unsigned long units);
|
|
static void clearTargetsNL(void);
|
|
static void clearFileImportNL(void);
|
|
static struct X11ClipboardWrite * takeFileWritesNL(void);
|
|
static struct X11ClipboardWrite * takeFileWritesForWindowNL(Window window);
|
|
static void cancelFileWrites(
|
|
struct X11ClipboardWrite * writes, bool terminate);
|
|
static void x11CBFileImportIncr(const XPropertyEvent e);
|
|
|
|
static const char * fileMimeForAtom(Atom atom)
|
|
{
|
|
if (atom == x11cb.aTypes[LG_CLIPBOARD_DATA_FILES])
|
|
return fileUriMime;
|
|
if (atom == x11cb.aFileGnome)
|
|
return fileGnomeMime;
|
|
if (atom == x11cb.aFileMate)
|
|
return fileMateMime;
|
|
if (atom == x11cb.aFileKde)
|
|
return fileKdeMime;
|
|
return NULL;
|
|
}
|
|
|
|
bool x11CBEventThread(const XEvent * xe)
|
|
{
|
|
switch(xe->type)
|
|
{
|
|
case SelectionRequest:
|
|
x11CBSelectionRequest(xe->xselectionrequest);
|
|
return true;
|
|
|
|
case SelectionClear:
|
|
x11CBSelectionClear(xe->xselectionclear);
|
|
return true;
|
|
|
|
case SelectionNotify:
|
|
x11CBSelectionNotify(xe->xselection);
|
|
return true;
|
|
|
|
case DestroyNotify:
|
|
{
|
|
LG_LOCK(x11cb.lock);
|
|
struct X11ClipboardWrite * writes =
|
|
takeFileWritesForWindowNL(xe->xdestroywindow.window);
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (!writes)
|
|
return false;
|
|
cancelFileWrites(writes, false);
|
|
return true;
|
|
}
|
|
|
|
case PropertyNotify:
|
|
if (xe->xproperty.state == PropertyNewValue &&
|
|
xe->xproperty.atom == x11atoms.SEL_DATA)
|
|
{
|
|
LG_LOCK(x11cb.lock);
|
|
const bool fileImport = x11cb.fileImport.window &&
|
|
xe->xproperty.window == x11cb.fileImport.window;
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (fileImport)
|
|
x11CBFileImportIncr(xe->xproperty);
|
|
else
|
|
x11CBSelectionIncr(xe->xproperty);
|
|
return true;
|
|
}
|
|
if (xe->xproperty.state == PropertyDelete)
|
|
{
|
|
LG_ClipboardRequest request = LG_CLIPBOARD_REQUEST_INVALID;
|
|
LG_LOCK(x11cb.lock);
|
|
struct X11ClipboardWrite * write;
|
|
for (write = x11cb.writes; write; write = write->next)
|
|
if (xe->xproperty.window == write->event.xselection.requestor &&
|
|
xe->xproperty.atom == write->event.xselection.property)
|
|
break;
|
|
if (write)
|
|
{
|
|
if (write->file)
|
|
{
|
|
const size_t remaining = write->fileSize - (size_t)write->offset;
|
|
if (remaining)
|
|
{
|
|
const size_t chunk =
|
|
remaining > KVMFR_CLIPBOARD_REPRESENTATION_BYTES ?
|
|
KVMFR_CLIPBOARD_REPRESENTATION_BYTES : remaining;
|
|
XChangeProperty(x11.display,
|
|
write->event.xselection.requestor,
|
|
write->event.xselection.property,
|
|
write->event.xselection.target, 8, PropModeReplace,
|
|
write->fileData + (size_t)write->offset, (int)chunk);
|
|
write->offset += chunk;
|
|
XFlush(x11.display);
|
|
LG_UNLOCK(x11cb.lock);
|
|
return true;
|
|
}
|
|
|
|
XChangeProperty(x11.display,
|
|
write->event.xselection.requestor,
|
|
write->event.xselection.property,
|
|
write->event.xselection.target, 8, PropModeReplace,
|
|
NULL, 0);
|
|
XFlush(x11.display);
|
|
if (write->event.xselection.target != x11cb.aFileKde)
|
|
clipboardFiles_remotePresentationDelivered(
|
|
write->filePresentation);
|
|
const uint64_t presentation = write->filePresentation;
|
|
writeRemoveNL(write);
|
|
LG_UNLOCK(x11cb.lock);
|
|
clipboardFiles_remotePresentationRelease(presentation);
|
|
free(write->fileData);
|
|
free(write);
|
|
return true;
|
|
}
|
|
|
|
write->ready = true;
|
|
if (write->blocked)
|
|
{
|
|
write->blocked = false;
|
|
request = write->request;
|
|
}
|
|
}
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (write)
|
|
{
|
|
if (request != LG_CLIPBOARD_REQUEST_INVALID)
|
|
clipboard_requestReady(request);
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (xe->type == x11.eventBase + XFixesSelectionNotify)
|
|
{
|
|
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)xe;
|
|
x11CBXFixesSelectionNotify(*sne);
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool x11CBInit(void)
|
|
{
|
|
LG_LOCK_INIT(x11cb.lock);
|
|
x11cb.aCurSelection = BadValue;
|
|
x11cb.read.request = LG_CLIPBOARD_REQUEST_INVALID;
|
|
x11cb.writes = NULL;
|
|
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
|
{
|
|
x11cb.aTypes[i] = XInternAtom(x11.display, atomTypes[i], False);
|
|
if (x11cb.aTypes[i] == BadAlloc || x11cb.aTypes[i] == BadValue)
|
|
{
|
|
DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
x11cb.aFileGnome = XInternAtom(x11.display, fileGnomeMime, False);
|
|
x11cb.aFileMate = XInternAtom(x11.display, fileMateMime , False);
|
|
x11cb.aFileKde = XInternAtom(x11.display, fileKdeMime , False);
|
|
if (
|
|
x11cb.aFileGnome == BadAlloc || x11cb.aFileGnome == BadValue ||
|
|
x11cb.aFileMate == BadAlloc || x11cb.aFileMate == BadValue ||
|
|
x11cb.aFileKde == BadAlloc || x11cb.aFileKde == BadValue)
|
|
{
|
|
DEBUG_ERROR("failed to get clipboard file atoms");
|
|
return false;
|
|
}
|
|
|
|
// use xfixes to get clipboard change notifications
|
|
if (!XFixesQueryExtension(x11.display, &x11.eventBase, &x11.errorBase))
|
|
{
|
|
DEBUG_ERROR("failed to initialize xfixes");
|
|
return false;
|
|
}
|
|
|
|
XFixesSelectSelectionInput(x11.display, x11.window,
|
|
x11atoms.CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void x11CBWriteSend(XEvent * event)
|
|
{
|
|
XSendEvent(x11.display, event->xselection.requestor, 0, 0, event);
|
|
XFlush(x11.display);
|
|
}
|
|
|
|
static LG_ClipboardResult x11CBWriteBegin(void * opaque,
|
|
LG_ClipboardData type, uint64_t sizeHint)
|
|
{
|
|
struct X11ClipboardWrite * write = opaque;
|
|
LG_LOCK(x11cb.lock);
|
|
if (!writeLinkedNL(write) ||
|
|
write->request == LG_CLIPBOARD_REQUEST_INVALID ||
|
|
write->begun || type != write->type)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return LG_CLIPBOARD_RESULT_FAILED;
|
|
}
|
|
|
|
const unsigned long hint = sizeHint == LG_CLIPBOARD_SIZE_UNKNOWN ? 0 :
|
|
(sizeHint > ULONG_MAX ? ULONG_MAX : (unsigned long)sizeHint);
|
|
XChangeProperty(x11.display, write->event.xselection.requestor,
|
|
write->event.xselection.property, x11atoms.INCR, 32,
|
|
PropModeReplace, (const unsigned char *)&hint, 1);
|
|
XSelectInput(x11.display, write->event.xselection.requestor,
|
|
PropertyChangeMask);
|
|
write->begun = true;
|
|
write->ready = false;
|
|
XEvent event = write->event;
|
|
LG_UNLOCK(x11cb.lock);
|
|
x11CBWriteSend(&event);
|
|
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
|
}
|
|
|
|
static LG_ClipboardResult x11CBWriteChunk(void * opaque,
|
|
uint64_t offset, const void * data, size_t size)
|
|
{
|
|
struct X11ClipboardWrite * write = opaque;
|
|
LG_LOCK(x11cb.lock);
|
|
if (!writeLinkedNL(write) ||
|
|
write->request == LG_CLIPBOARD_REQUEST_INVALID || !write->begun ||
|
|
write->endPending || !data || !size)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return LG_CLIPBOARD_RESULT_FAILED;
|
|
}
|
|
if (!write->ready)
|
|
{
|
|
write->blocked = true;
|
|
LG_UNLOCK(x11cb.lock);
|
|
return LG_CLIPBOARD_RESULT_BLOCKED;
|
|
}
|
|
if (offset != write->offset ||
|
|
size > KVMFR_CLIPBOARD_REPRESENTATION_BYTES)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return LG_CLIPBOARD_RESULT_FAILED;
|
|
}
|
|
|
|
XChangeProperty(x11.display, write->event.xselection.requestor,
|
|
write->event.xselection.property, write->event.xselection.target,
|
|
8, PropModeReplace, data, (int)size);
|
|
write->ready = false;
|
|
write->blocked = false;
|
|
write->offset += size;
|
|
LG_UNLOCK(x11cb.lock);
|
|
XFlush(x11.display);
|
|
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
|
}
|
|
|
|
static LG_ClipboardResult x11CBWriteEnd(void * opaque, uint64_t finalSize)
|
|
{
|
|
struct X11ClipboardWrite * write = opaque;
|
|
LG_LOCK(x11cb.lock);
|
|
if (!writeLinkedNL(write) ||
|
|
write->request == LG_CLIPBOARD_REQUEST_INVALID || !write->begun)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return LG_CLIPBOARD_RESULT_FAILED;
|
|
}
|
|
if (!write->ready)
|
|
{
|
|
write->blocked = true;
|
|
write->endPending = true;
|
|
LG_UNLOCK(x11cb.lock);
|
|
return LG_CLIPBOARD_RESULT_BLOCKED;
|
|
}
|
|
if (finalSize != write->offset)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return LG_CLIPBOARD_RESULT_FAILED;
|
|
}
|
|
|
|
const XEvent event = write->event;
|
|
const bool destroy = !write->creating;
|
|
writeRemoveNL(write);
|
|
LG_UNLOCK(x11cb.lock);
|
|
XChangeProperty(x11.display, event.xselection.requestor,
|
|
event.xselection.property, event.xselection.target, 8,
|
|
PropModeReplace, NULL, 0);
|
|
XFlush(x11.display);
|
|
if (destroy)
|
|
free(write);
|
|
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
|
}
|
|
|
|
static void x11CBWriteCancel(void * opaque,
|
|
LG_ClipboardCancelReason reason)
|
|
{
|
|
struct X11ClipboardWrite * write = opaque;
|
|
(void)reason;
|
|
LG_LOCK(x11cb.lock);
|
|
if (!writeLinkedNL(write))
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
const XEvent event = write->event;
|
|
const bool begun = write->begun;
|
|
const bool destroy = !write->creating;
|
|
writeRemoveNL(write);
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (begun)
|
|
{
|
|
XChangeProperty(x11.display, event.xselection.requestor,
|
|
event.xselection.property, event.xselection.target, 8,
|
|
PropModeReplace, NULL, 0);
|
|
XFlush(x11.display);
|
|
}
|
|
else
|
|
{
|
|
XEvent reply = event;
|
|
reply.xselection.property = None;
|
|
x11CBWriteSend(&reply);
|
|
}
|
|
if (destroy)
|
|
free(write);
|
|
}
|
|
|
|
static const LG_ClipboardStreamOps x11CBWriteStream =
|
|
{
|
|
.begin = x11CBWriteBegin,
|
|
.chunk = x11CBWriteChunk,
|
|
.end = x11CBWriteEnd,
|
|
.cancel = x11CBWriteCancel,
|
|
};
|
|
|
|
static void x11CBSelectionRequest(const XSelectionRequestEvent e)
|
|
{
|
|
XEvent * s = malloc(sizeof(*s));
|
|
if (!s)
|
|
{
|
|
DEBUG_ERROR("out of memory");
|
|
return;
|
|
}
|
|
|
|
s->xselection.type = SelectionNotify;
|
|
s->xselection.requestor = e.requestor;
|
|
s->xselection.selection = e.selection;
|
|
s->xselection.target = e.target;
|
|
s->xselection.property = e.property;
|
|
s->xselection.time = e.time;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
const bool haveRequest = x11cb.haveRequest;
|
|
const LG_ClipboardData requestType = x11cb.type;
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
if (!haveRequest)
|
|
goto nodata;
|
|
|
|
// target list requested
|
|
if (e.target == x11atoms.TARGETS)
|
|
{
|
|
Atom targets[5];
|
|
targets[0] = x11atoms.TARGETS;
|
|
targets[1] = x11cb.aTypes[requestType];
|
|
int count = 2;
|
|
if (requestType == LG_CLIPBOARD_DATA_FILES)
|
|
{
|
|
targets[count++] = x11cb.aFileGnome;
|
|
targets[count++] = x11cb.aFileMate;
|
|
targets[count++] = x11cb.aFileKde;
|
|
}
|
|
|
|
XChangeProperty(
|
|
e.display,
|
|
e.requestor,
|
|
e.property,
|
|
XA_ATOM,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char*)targets,
|
|
count
|
|
);
|
|
|
|
goto send;
|
|
}
|
|
|
|
const char * fileMime = fileMimeForAtom(e.target);
|
|
if (requestType == LG_CLIPBOARD_DATA_FILES && fileMime)
|
|
{
|
|
char * data = NULL;
|
|
size_t size = 0;
|
|
uint64_t writePresentation = 0;
|
|
LG_LOCK(x11cb.lock);
|
|
const uint64_t presentation = x11cb.filePresentation;
|
|
bool available = x11cb.haveRequest &&
|
|
x11cb.type == LG_CLIPBOARD_DATA_FILES && presentation;
|
|
if (available)
|
|
{
|
|
writePresentation =
|
|
clipboardFiles_remotePresentationAcquire();
|
|
available = writePresentation == presentation &&
|
|
clipboardFiles_getRemotePresentation(writePresentation,
|
|
fileMime, &data, &size);
|
|
}
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (!available)
|
|
{
|
|
free(data);
|
|
if (writePresentation)
|
|
clipboardFiles_remotePresentationRelease(writePresentation);
|
|
goto nodata;
|
|
}
|
|
|
|
struct X11ClipboardWrite * write = calloc(1, sizeof(*write));
|
|
if (!write)
|
|
{
|
|
free(data);
|
|
clipboardFiles_remotePresentationRelease(writePresentation);
|
|
DEBUG_ERROR("out of memory");
|
|
goto nodata;
|
|
}
|
|
write->event = *s;
|
|
write->request = LG_CLIPBOARD_REQUEST_INVALID;
|
|
write->type = LG_CLIPBOARD_DATA_FILES;
|
|
write->file = true;
|
|
write->fileData = (uint8_t *)data;
|
|
write->fileSize = size;
|
|
write->filePresentation = writePresentation;
|
|
write->begun = true;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
bool duplicate = false;
|
|
for (struct X11ClipboardWrite * current = x11cb.writes;
|
|
current; current = current->next)
|
|
{
|
|
duplicate = current->event.xselection.requestor == e.requestor &&
|
|
current->event.xselection.property == e.property;
|
|
if (duplicate)
|
|
break;
|
|
}
|
|
if (!duplicate && x11cb.haveRequest &&
|
|
x11cb.type == LG_CLIPBOARD_DATA_FILES &&
|
|
x11cb.filePresentation == presentation)
|
|
{
|
|
write->next = x11cb.writes;
|
|
x11cb.writes = write;
|
|
const unsigned long hint = size > UINT32_MAX ?
|
|
UINT32_MAX : (unsigned long)size;
|
|
XChangeProperty(x11.display, e.requestor, e.property,
|
|
x11atoms.INCR, 32, PropModeReplace,
|
|
(const unsigned char *)&hint, 1);
|
|
XSelectInput(x11.display, e.requestor,
|
|
PropertyChangeMask | StructureNotifyMask);
|
|
}
|
|
else
|
|
duplicate = true;
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
if (duplicate)
|
|
{
|
|
free(write->fileData);
|
|
clipboardFiles_remotePresentationRelease(
|
|
write->filePresentation);
|
|
free(write);
|
|
goto nodata;
|
|
}
|
|
|
|
x11CBWriteSend(s);
|
|
free(s);
|
|
return;
|
|
}
|
|
|
|
// look to see if we can satisfy the data type
|
|
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
|
if (x11cb.aTypes[i] == e.target && requestType == i)
|
|
{
|
|
struct X11ClipboardWrite * write = calloc(1, sizeof(*write));
|
|
if (!write)
|
|
{
|
|
DEBUG_ERROR("out of memory");
|
|
goto nodata;
|
|
}
|
|
write->event = *s;
|
|
write->request = LG_CLIPBOARD_REQUEST_INVALID;
|
|
write->type = requestType;
|
|
write->creating = true;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
bool duplicate = false;
|
|
for (struct X11ClipboardWrite * current = x11cb.writes;
|
|
current; current = current->next)
|
|
{
|
|
duplicate = current->event.xselection.requestor == e.requestor &&
|
|
current->event.xselection.property == e.property;
|
|
if (duplicate)
|
|
break;
|
|
}
|
|
if (!duplicate)
|
|
{
|
|
write->next = x11cb.writes;
|
|
x11cb.writes = write;
|
|
}
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (duplicate)
|
|
{
|
|
free(write);
|
|
goto nodata;
|
|
}
|
|
|
|
const bool result = clipboard_requestStream(requestType,
|
|
&x11CBWriteStream, write, &write->request);
|
|
LG_LOCK(x11cb.lock);
|
|
const bool linked = writeLinkedNL(write);
|
|
if (linked)
|
|
{
|
|
write->creating = false;
|
|
if (!result)
|
|
writeRemoveNL(write);
|
|
}
|
|
else
|
|
write->creating = false;
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
if (result && linked)
|
|
{
|
|
free(s);
|
|
return;
|
|
}
|
|
free(write);
|
|
if (!linked)
|
|
{
|
|
free(s);
|
|
return;
|
|
}
|
|
goto nodata;
|
|
}
|
|
|
|
nodata:
|
|
// report no data
|
|
s->xselection.property = None;
|
|
|
|
send:
|
|
XSendEvent(x11.display, e.requestor, 0, 0, s);
|
|
XFlush(x11.display);
|
|
free(s);
|
|
}
|
|
|
|
static void x11CBSelectionClear(const XSelectionClearEvent e)
|
|
{
|
|
if (e.selection != x11atoms.CLIPBOARD)
|
|
return;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
if (XGetSelectionOwner(x11.display, x11atoms.CLIPBOARD) == x11.window)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
x11cb.haveRequest = false;
|
|
const uint64_t presentation = x11cb.filePresentation;
|
|
x11cb.filePresentation = 0;
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
if (presentation)
|
|
clipboardFiles_remotePresentationRelease(presentation);
|
|
}
|
|
|
|
/* x11cb.lock must be held. */
|
|
static struct X11ClipboardRead clearReadNL(void)
|
|
{
|
|
const struct X11ClipboardRead read = x11cb.read;
|
|
x11cb.read = (struct X11ClipboardRead)
|
|
{
|
|
.request = LG_CLIPBOARD_REQUEST_INVALID,
|
|
};
|
|
if (read.window)
|
|
XDestroyWindow(x11.display, read.window);
|
|
return read;
|
|
}
|
|
|
|
/* x11cb.lock must be held. */
|
|
static LG_ClipboardRequest cancelReadNL(bool abort)
|
|
{
|
|
const struct X11ClipboardRead read = clearReadNL();
|
|
free(read.pending);
|
|
return abort ? read.request : LG_CLIPBOARD_REQUEST_INVALID;
|
|
}
|
|
|
|
/* x11cb.lock must be held. */
|
|
static bool writeLinkedNL(const struct X11ClipboardWrite * write)
|
|
{
|
|
for (const struct X11ClipboardWrite * current = x11cb.writes;
|
|
current; current = current->next)
|
|
if (current == write)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* x11cb.lock must be held and write must be linked. */
|
|
static void writeRemoveNL(struct X11ClipboardWrite * write)
|
|
{
|
|
struct X11ClipboardWrite ** current = &x11cb.writes;
|
|
while (*current != write)
|
|
current = &(*current)->next;
|
|
*current = write->next;
|
|
write->next = NULL;
|
|
}
|
|
|
|
/* x11cb.lock must be held. */
|
|
static bool advanceReadPropertyNL(unsigned long units)
|
|
{
|
|
if (units > LONG_MAX ||
|
|
x11cb.read.propertyOffset > LONG_MAX - (long)units)
|
|
return false;
|
|
x11cb.read.propertyOffset += (long)units;
|
|
return true;
|
|
}
|
|
|
|
/* x11cb.lock must be held. */
|
|
static void clearTargetsNL(void)
|
|
{
|
|
if (!x11cb.targetsWindow)
|
|
return;
|
|
|
|
XDestroyWindow(x11.display, x11cb.targetsWindow);
|
|
x11cb.targetsWindow = 0;
|
|
}
|
|
|
|
/* x11cb.lock must be held. */
|
|
static struct X11ClipboardFileImport takeFileImportNL(void)
|
|
{
|
|
const struct X11ClipboardFileImport import = x11cb.fileImport;
|
|
x11cb.fileImport = (struct X11ClipboardFileImport) { 0 };
|
|
if (import.window)
|
|
XDestroyWindow(x11.display, import.window);
|
|
return import;
|
|
}
|
|
|
|
/* x11cb.lock must be held. */
|
|
static void clearFileImportNL(void)
|
|
{
|
|
const struct X11ClipboardFileImport import = takeFileImportNL();
|
|
free(import.data);
|
|
}
|
|
|
|
/* x11cb.lock must be held. */
|
|
static struct X11ClipboardWrite * takeFileWritesNL(void)
|
|
{
|
|
struct X11ClipboardWrite * result = NULL;
|
|
struct X11ClipboardWrite ** link = &x11cb.writes;
|
|
while (*link)
|
|
{
|
|
struct X11ClipboardWrite * write = *link;
|
|
if (!write->file)
|
|
{
|
|
link = &write->next;
|
|
continue;
|
|
}
|
|
|
|
*link = write->next;
|
|
write->next = result;
|
|
result = write;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* x11cb.lock must be held. */
|
|
static struct X11ClipboardWrite * takeFileWritesForWindowNL(Window window)
|
|
{
|
|
struct X11ClipboardWrite * result = NULL;
|
|
struct X11ClipboardWrite ** link = &x11cb.writes;
|
|
while (*link)
|
|
{
|
|
struct X11ClipboardWrite * write = *link;
|
|
if (!write->file ||
|
|
write->event.xselection.requestor != window)
|
|
{
|
|
link = &write->next;
|
|
continue;
|
|
}
|
|
|
|
*link = write->next;
|
|
write->next = result;
|
|
result = write;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void cancelFileWrites(
|
|
struct X11ClipboardWrite * writes, bool terminate)
|
|
{
|
|
const bool flush = terminate && writes;
|
|
while (writes)
|
|
{
|
|
struct X11ClipboardWrite * next = writes->next;
|
|
if (terminate)
|
|
XChangeProperty(x11.display, writes->event.xselection.requestor,
|
|
writes->event.xselection.property,
|
|
writes->event.xselection.target, 8, PropModeReplace, NULL, 0);
|
|
clipboardFiles_remotePresentationRelease(
|
|
writes->filePresentation);
|
|
free(writes->fileData);
|
|
free(writes);
|
|
writes = next;
|
|
}
|
|
if (flush)
|
|
XFlush(x11.display);
|
|
}
|
|
|
|
static bool fileImportGrowNL(size_t wanted)
|
|
{
|
|
if (wanted <= x11cb.fileImport.capacity)
|
|
return true;
|
|
size_t capacity = x11cb.fileImport.capacity ?
|
|
x11cb.fileImport.capacity : 4096U;
|
|
while (capacity < wanted)
|
|
{
|
|
if (capacity > SIZE_MAX / 2U)
|
|
{
|
|
capacity = wanted;
|
|
break;
|
|
}
|
|
capacity *= 2U;
|
|
}
|
|
uint8_t * data = realloc(x11cb.fileImport.data, capacity);
|
|
if (!data)
|
|
return false;
|
|
x11cb.fileImport.data = data;
|
|
x11cb.fileImport.capacity = capacity;
|
|
return true;
|
|
}
|
|
|
|
static bool startFileImport(Atom target,
|
|
const LG_ClipboardData types[], size_t typeCount)
|
|
{
|
|
const char * mime = fileMimeForAtom(target);
|
|
if (!mime)
|
|
return false;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
clearFileImportNL();
|
|
if (x11cb.aCurSelection == BadValue)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return false;
|
|
}
|
|
|
|
const Window window = XCreateSimpleWindow(
|
|
x11.display, x11.window, 0, 0, 1, 1, 0, 0, 0);
|
|
if (!window)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return false;
|
|
}
|
|
|
|
x11cb.fileImport = (struct X11ClipboardFileImport)
|
|
{
|
|
.window = window,
|
|
.target = target,
|
|
.mime = mime,
|
|
};
|
|
memcpy(x11cb.fileImport.types, types, typeCount * sizeof(*types));
|
|
x11cb.fileImport.typeCount = typeCount;
|
|
XSelectInput(x11.display, window, PropertyChangeMask);
|
|
XConvertSelection(x11.display, x11cb.aCurSelection, target,
|
|
x11atoms.SEL_DATA, window, CurrentTime);
|
|
XFlush(x11.display);
|
|
LG_UNLOCK(x11cb.lock);
|
|
return true;
|
|
}
|
|
|
|
static void finishFileImport(struct X11ClipboardFileImport import,
|
|
const void * data, size_t size)
|
|
{
|
|
if (!clipboardFiles_setLocal(import.mime, data, size))
|
|
{
|
|
clipboardFiles_clearLocal();
|
|
clipboard_notifyTypes(import.types, import.typeCount);
|
|
}
|
|
free(import.data);
|
|
}
|
|
|
|
static void failFileImport(struct X11ClipboardFileImport import)
|
|
{
|
|
free(import.data);
|
|
clipboardFiles_clearLocal();
|
|
clipboard_notifyTypes(import.types, import.typeCount);
|
|
}
|
|
|
|
static bool x11CBFileImportSelectionNotify(const XSelectionEvent e)
|
|
{
|
|
Atom type;
|
|
int format;
|
|
unsigned long itemCount;
|
|
unsigned long after;
|
|
unsigned char * data = NULL;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
if (!x11cb.fileImport.window || e.requestor != x11cb.fileImport.window)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return false;
|
|
}
|
|
|
|
if (e.target != x11cb.fileImport.target ||
|
|
e.property != x11atoms.SEL_DATA ||
|
|
XGetWindowProperty(e.display, e.requestor, e.property, 0, ~0L,
|
|
True, AnyPropertyType, &type, &format, &itemCount, &after,
|
|
&data) != Success)
|
|
{
|
|
const struct X11ClipboardFileImport failed = takeFileImportNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (data)
|
|
XFree(data);
|
|
failFileImport(failed);
|
|
return true;
|
|
}
|
|
|
|
if (type == x11atoms.INCR)
|
|
{
|
|
if (!data || format != 32 || itemCount < 1 || after)
|
|
{
|
|
const struct X11ClipboardFileImport failed = takeFileImportNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (data)
|
|
XFree(data);
|
|
failFileImport(failed);
|
|
return true;
|
|
}
|
|
x11cb.fileImport.incremental = true;
|
|
XFree(data);
|
|
LG_UNLOCK(x11cb.lock);
|
|
return true;
|
|
}
|
|
|
|
const bool valid = type == x11cb.fileImport.target && format == 8 &&
|
|
!after && (!itemCount || data);
|
|
const struct X11ClipboardFileImport import = takeFileImportNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (valid)
|
|
finishFileImport(import, data, itemCount);
|
|
else
|
|
failFileImport(import);
|
|
if (data)
|
|
XFree(data);
|
|
return true;
|
|
}
|
|
|
|
static void x11CBFileImportIncr(const XPropertyEvent e)
|
|
{
|
|
Atom type;
|
|
int format;
|
|
unsigned long itemCount;
|
|
unsigned long after;
|
|
unsigned char * data = NULL;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
if (!x11cb.fileImport.window || !x11cb.fileImport.incremental ||
|
|
e.window != x11cb.fileImport.window || e.atom != x11atoms.SEL_DATA)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
readProperty:
|
|
if (XGetWindowProperty(e.display, e.window, e.atom,
|
|
x11cb.fileImport.propertyOffset,
|
|
(KVMFR_CLIPBOARD_REPRESENTATION_BYTES + 3U) / 4U,
|
|
True, AnyPropertyType, &type, &format, &itemCount, &after,
|
|
&data) != Success || (itemCount && !data) || (!itemCount && after) ||
|
|
type != x11cb.fileImport.target || format != 8 ||
|
|
itemCount > SIZE_MAX ||
|
|
(size_t)itemCount > SIZE_MAX - x11cb.fileImport.size ||
|
|
!fileImportGrowNL(x11cb.fileImport.size + (size_t)itemCount))
|
|
{
|
|
const struct X11ClipboardFileImport failed = takeFileImportNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (data)
|
|
XFree(data);
|
|
failFileImport(failed);
|
|
return;
|
|
}
|
|
|
|
if (itemCount)
|
|
{
|
|
memcpy(x11cb.fileImport.data + x11cb.fileImport.size,
|
|
data, (size_t)itemCount);
|
|
x11cb.fileImport.size += (size_t)itemCount;
|
|
}
|
|
if (data)
|
|
{
|
|
XFree(data);
|
|
data = NULL;
|
|
}
|
|
|
|
if (after)
|
|
{
|
|
const unsigned long units = itemCount / 4U +
|
|
(itemCount % 4U != 0);
|
|
if (units > LONG_MAX ||
|
|
x11cb.fileImport.propertyOffset > LONG_MAX - (long)units)
|
|
{
|
|
const struct X11ClipboardFileImport failed = takeFileImportNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
failFileImport(failed);
|
|
return;
|
|
}
|
|
x11cb.fileImport.propertyOffset += (long)units;
|
|
goto readProperty;
|
|
}
|
|
|
|
x11cb.fileImport.propertyOffset = 0;
|
|
if (itemCount)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
const struct X11ClipboardFileImport import = takeFileImportNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
finishFileImport(import, import.data, import.size);
|
|
}
|
|
|
|
enum X11ClipboardReadOperation
|
|
{
|
|
X11_CLIPBOARD_READ_BEGIN,
|
|
X11_CLIPBOARD_READ_CHUNK,
|
|
X11_CLIPBOARD_READ_END,
|
|
};
|
|
|
|
enum X11ClipboardReadCallResult
|
|
{
|
|
X11_CLIPBOARD_READ_CALL_STALE,
|
|
X11_CLIPBOARD_READ_CALL_ACCEPTED,
|
|
X11_CLIPBOARD_READ_CALL_BLOCKED,
|
|
X11_CLIPBOARD_READ_CALL_RETRY,
|
|
X11_CLIPBOARD_READ_CALL_FAILED,
|
|
};
|
|
|
|
/* x11cb.lock must be held on entry and is held again on return. Retain a ready
|
|
* edge delivered between the provider operation and committing BLOCKED. */
|
|
static enum X11ClipboardReadCallResult x11CBReadCallNL(
|
|
enum X11ClipboardReadOperation operation, LG_ClipboardData type,
|
|
uint64_t sizeHint, uint64_t offset, const void * data, size_t size)
|
|
{
|
|
const LG_ClipboardRequest request = x11cb.read.request;
|
|
x11cb.read.calling = true;
|
|
x11cb.read.readyPending = false;
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
LG_ClipboardResult result;
|
|
switch (operation)
|
|
{
|
|
case X11_CLIPBOARD_READ_BEGIN:
|
|
result = clipboard_dataBegin(request, type, sizeHint);
|
|
break;
|
|
|
|
case X11_CLIPBOARD_READ_CHUNK:
|
|
result = clipboard_dataChunk(request, offset, data, size);
|
|
break;
|
|
|
|
case X11_CLIPBOARD_READ_END:
|
|
result = clipboard_dataEnd(request, offset);
|
|
break;
|
|
|
|
default:
|
|
result = LG_CLIPBOARD_RESULT_FAILED;
|
|
break;
|
|
}
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
if (x11cb.read.request != request)
|
|
return X11_CLIPBOARD_READ_CALL_STALE;
|
|
|
|
x11cb.read.calling = false;
|
|
if (result == LG_CLIPBOARD_RESULT_BLOCKED)
|
|
{
|
|
if (x11cb.read.readyPending)
|
|
{
|
|
x11cb.read.readyPending = false;
|
|
return X11_CLIPBOARD_READ_CALL_RETRY;
|
|
}
|
|
|
|
x11cb.read.blocked = true;
|
|
return X11_CLIPBOARD_READ_CALL_BLOCKED;
|
|
}
|
|
|
|
x11cb.read.readyPending = false;
|
|
return result == LG_CLIPBOARD_RESULT_ACCEPTED ?
|
|
X11_CLIPBOARD_READ_CALL_ACCEPTED : X11_CLIPBOARD_READ_CALL_FAILED;
|
|
}
|
|
|
|
static void x11CBSelectionIncr(const XPropertyEvent e)
|
|
{
|
|
Atom type;
|
|
int format;
|
|
unsigned long itemCount;
|
|
unsigned long after;
|
|
unsigned char * data = NULL;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
if (!x11cb.read.window || !x11cb.read.incremental ||
|
|
e.window != x11cb.read.window ||
|
|
e.atom != x11atoms.SEL_DATA)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
if (x11cb.read.blocked || x11cb.read.calling || x11cb.read.pendingSize ||
|
|
x11cb.read.endPending)
|
|
{
|
|
x11cb.read.propertyPending = true;
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
readProperty:
|
|
if (XGetWindowProperty(
|
|
e.display,
|
|
e.window,
|
|
e.atom,
|
|
x11cb.read.propertyOffset,
|
|
(KVMFR_CLIPBOARD_REPRESENTATION_BYTES + 3U) / 4U,
|
|
True, // delete the property
|
|
AnyPropertyType,
|
|
&type,
|
|
&format,
|
|
&itemCount,
|
|
&after,
|
|
&data) != Success)
|
|
{
|
|
DEBUG_ERROR("XGetWindowProperty Failed");
|
|
const struct X11ClipboardRead read = clearReadNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
free(read.pending);
|
|
clipboard_abort(read.request);
|
|
return;
|
|
}
|
|
|
|
LG_ClipboardData dataType;
|
|
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
|
if (x11cb.aTypes[dataType] == type)
|
|
break;
|
|
|
|
if ((itemCount && !data) || (!itemCount && after) ||
|
|
dataType == LG_CLIPBOARD_DATA_NONE ||
|
|
dataType != x11cb.read.type || format != 8)
|
|
{
|
|
DEBUG_WARN("Invalid incremental clipboard data");
|
|
const struct X11ClipboardRead read = clearReadNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (data)
|
|
XFree(data);
|
|
free(read.pending);
|
|
clipboard_abort(read.request);
|
|
return;
|
|
}
|
|
|
|
if (itemCount == 0)
|
|
{
|
|
if (data)
|
|
XFree(data);
|
|
const uint64_t offset = x11cb.read.offset;
|
|
enum X11ClipboardReadCallResult result;
|
|
do
|
|
result = x11CBReadCallNL(X11_CLIPBOARD_READ_END,
|
|
x11cb.read.type, x11cb.read.sizeHint, offset, NULL, 0);
|
|
while (result == X11_CLIPBOARD_READ_CALL_RETRY);
|
|
if (result == X11_CLIPBOARD_READ_CALL_STALE)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
if (result == X11_CLIPBOARD_READ_CALL_BLOCKED)
|
|
{
|
|
x11cb.read.endPending = true;
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
cancelReadNL(false);
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
if (itemCount > KVMFR_CLIPBOARD_REPRESENTATION_BYTES)
|
|
{
|
|
const struct X11ClipboardRead read = clearReadNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
XFree(data);
|
|
free(read.pending);
|
|
clipboard_abort(read.request);
|
|
return;
|
|
}
|
|
|
|
uint8_t * copy = malloc(itemCount);
|
|
if (!copy)
|
|
{
|
|
const struct X11ClipboardRead read = clearReadNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
XFree(data);
|
|
free(read.pending);
|
|
clipboard_abort(read.request);
|
|
return;
|
|
}
|
|
memcpy(copy, data, itemCount);
|
|
XFree(data);
|
|
data = NULL;
|
|
|
|
const uint64_t offset = x11cb.read.offset;
|
|
enum X11ClipboardReadCallResult result;
|
|
do
|
|
result = x11CBReadCallNL(X11_CLIPBOARD_READ_CHUNK,
|
|
x11cb.read.type, x11cb.read.sizeHint,
|
|
offset, copy, itemCount);
|
|
while (result == X11_CLIPBOARD_READ_CALL_RETRY);
|
|
if (result == X11_CLIPBOARD_READ_CALL_STALE)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
free(copy);
|
|
return;
|
|
}
|
|
if (result == X11_CLIPBOARD_READ_CALL_BLOCKED)
|
|
{
|
|
x11cb.read.pending = copy;
|
|
x11cb.read.pendingSize = itemCount;
|
|
x11cb.read.pendingUnits = (itemCount + 3U) / 4U;
|
|
x11cb.read.pendingMore = after != 0;
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
free(copy);
|
|
if (result != X11_CLIPBOARD_READ_CALL_ACCEPTED)
|
|
{
|
|
cancelReadNL(false);
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
x11cb.read.offset += itemCount;
|
|
if (after)
|
|
{
|
|
if (!advanceReadPropertyNL((itemCount + 3U) / 4U))
|
|
{
|
|
const struct X11ClipboardRead read = clearReadNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
free(read.pending);
|
|
clipboard_abort(read.request);
|
|
return;
|
|
}
|
|
goto readProperty;
|
|
}
|
|
x11cb.read.propertyOffset = 0;
|
|
LG_UNLOCK(x11cb.lock);
|
|
}
|
|
|
|
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
|
|
{
|
|
if (e.selection != x11atoms.CLIPBOARD || e.owner == x11.window)
|
|
return;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
XGrabServer(x11.display);
|
|
if (XGetSelectionOwner(x11.display, x11atoms.CLIPBOARD) != e.owner)
|
|
{
|
|
XUngrabServer(x11.display);
|
|
XFlush(x11.display);
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
const LG_ClipboardRequest oldRequest = x11cb.read.window ?
|
|
cancelReadNL(true) : LG_CLIPBOARD_REQUEST_INVALID;
|
|
clearTargetsNL();
|
|
clearFileImportNL();
|
|
x11cb.haveRequest = false;
|
|
const uint64_t presentation = x11cb.filePresentation;
|
|
x11cb.filePresentation = 0;
|
|
|
|
if (e.owner == 0)
|
|
{
|
|
x11cb.aCurSelection = BadValue;
|
|
XUngrabServer(x11.display);
|
|
XFlush(x11.display);
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
|
clipboard_abort(oldRequest);
|
|
if (presentation)
|
|
clipboardFiles_remotePresentationRelease(presentation);
|
|
clipboardFiles_clearLocal();
|
|
clipboard_release();
|
|
return;
|
|
}
|
|
|
|
// remember which selection we are working with
|
|
x11cb.aCurSelection = e.selection;
|
|
x11cb.targetsWindow = XCreateSimpleWindow(
|
|
x11.display, x11.window, 0, 0, 1, 1, 0, 0, 0);
|
|
if (!x11cb.targetsWindow)
|
|
{
|
|
x11cb.aCurSelection = BadValue;
|
|
XUngrabServer(x11.display);
|
|
XFlush(x11.display);
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
|
clipboard_abort(oldRequest);
|
|
if (presentation)
|
|
clipboardFiles_remotePresentationRelease(presentation);
|
|
clipboardFiles_clearLocal();
|
|
clipboard_release();
|
|
return;
|
|
}
|
|
|
|
XConvertSelection(
|
|
x11.display,
|
|
e.selection,
|
|
x11atoms.TARGETS,
|
|
x11atoms.TARGETS,
|
|
x11cb.targetsWindow,
|
|
CurrentTime);
|
|
XUngrabServer(x11.display);
|
|
XFlush(x11.display);
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
|
clipboard_abort(oldRequest);
|
|
if (presentation)
|
|
clipboardFiles_remotePresentationRelease(presentation);
|
|
clipboardFiles_clearLocal();
|
|
}
|
|
|
|
static void x11CBSelectionNotify(const XSelectionEvent e)
|
|
{
|
|
Atom type;
|
|
int format;
|
|
unsigned long itemCount;
|
|
unsigned long after;
|
|
unsigned char * data = NULL;
|
|
|
|
if (x11CBFileImportSelectionNotify(e))
|
|
return;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
const bool targetReply = x11cb.targetsWindow &&
|
|
e.requestor == x11cb.targetsWindow && e.target == x11atoms.TARGETS;
|
|
if (targetReply)
|
|
{
|
|
if (e.property == None)
|
|
{
|
|
clearTargetsNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
clipboard_release();
|
|
return;
|
|
}
|
|
|
|
if (XGetWindowProperty(
|
|
e.display,
|
|
e.requestor,
|
|
e.property,
|
|
0, ~0L, // start and length
|
|
True, // delete the property
|
|
AnyPropertyType,
|
|
&type,
|
|
&format,
|
|
&itemCount,
|
|
&after,
|
|
&data) != Success)
|
|
{
|
|
clearTargetsNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
clipboard_release();
|
|
return;
|
|
}
|
|
clearTargetsNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
// the format is 32-bit and we must have data
|
|
// this is technically incorrect however as it's
|
|
// an array of padded 64-bit values
|
|
if (!data || format != 32)
|
|
{
|
|
clipboard_release();
|
|
goto out;
|
|
}
|
|
|
|
size_t typeCount = 0;
|
|
LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
|
|
|
|
// see if we support any of the targets listed
|
|
const Atom * targets = (const Atom *)data;
|
|
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
|
|
{
|
|
if (n == LG_CLIPBOARD_DATA_FILES)
|
|
continue;
|
|
for(unsigned long i = 0; i < itemCount; ++i)
|
|
if (x11cb.aTypes[n] == targets[i])
|
|
{
|
|
types[typeCount++] = n;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Atom fileTarget = None;
|
|
for (unsigned long i = 0; i < itemCount; ++i)
|
|
{
|
|
if (targets[i] == x11cb.aFileGnome)
|
|
{
|
|
fileTarget = x11cb.aFileGnome;
|
|
break;
|
|
}
|
|
|
|
if (targets[i] == x11cb.aFileMate)
|
|
{
|
|
fileTarget = x11cb.aFileMate;
|
|
break;
|
|
}
|
|
}
|
|
if (fileTarget == None)
|
|
for (unsigned long i = 0; i < itemCount; ++i)
|
|
if (targets[i] == x11cb.aTypes[LG_CLIPBOARD_DATA_FILES])
|
|
{
|
|
fileTarget = x11cb.aTypes[LG_CLIPBOARD_DATA_FILES];
|
|
break;
|
|
}
|
|
|
|
if (fileTarget != None)
|
|
{
|
|
clipboardFiles_clearLocal();
|
|
if (!startFileImport(fileTarget, types, typeCount))
|
|
clipboard_notifyTypes(types, typeCount);
|
|
goto out;
|
|
}
|
|
|
|
clipboard_notifyTypes(types, typeCount);
|
|
goto out;
|
|
}
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
if (!x11cb.read.window || e.requestor != x11cb.read.window)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
if (e.property == None)
|
|
{
|
|
const LG_ClipboardRequest request = cancelReadNL(true);
|
|
LG_UNLOCK(x11cb.lock);
|
|
clipboard_abort(request);
|
|
return;
|
|
}
|
|
|
|
if (XGetWindowProperty(
|
|
e.display,
|
|
e.requestor,
|
|
e.property,
|
|
0, ~0L, // start and length
|
|
True , // delete the property
|
|
AnyPropertyType,
|
|
&type,
|
|
&format,
|
|
&itemCount,
|
|
&after,
|
|
&data) != Success)
|
|
{
|
|
const LG_ClipboardRequest request = cancelReadNL(true);
|
|
LG_UNLOCK(x11cb.lock);
|
|
clipboard_abort(request);
|
|
return;
|
|
}
|
|
|
|
if (type == x11atoms.INCR)
|
|
{
|
|
if (!data || format != 32 || itemCount < 1)
|
|
{
|
|
const LG_ClipboardRequest request = cancelReadNL(true);
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (data)
|
|
XFree(data);
|
|
clipboard_abort(request);
|
|
return;
|
|
}
|
|
|
|
const uint64_t hint = *(const unsigned long *)data;
|
|
const uint64_t sizeHint = hint ? hint : LG_CLIPBOARD_SIZE_UNKNOWN;
|
|
const LG_ClipboardData dataType = x11cb.read.type;
|
|
x11cb.read.incremental = true;
|
|
x11cb.read.sizeHint = sizeHint;
|
|
XFree(data);
|
|
enum X11ClipboardReadCallResult result;
|
|
do
|
|
result = x11CBReadCallNL(X11_CLIPBOARD_READ_BEGIN,
|
|
dataType, sizeHint, 0, NULL, 0);
|
|
while (result == X11_CLIPBOARD_READ_CALL_RETRY);
|
|
if (result == X11_CLIPBOARD_READ_CALL_STALE)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
if (result == X11_CLIPBOARD_READ_CALL_BLOCKED)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
if (result != X11_CLIPBOARD_READ_CALL_ACCEPTED)
|
|
{
|
|
cancelReadNL(false);
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
x11cb.read.begun = true;
|
|
const bool processProperty = x11cb.read.propertyPending;
|
|
const Window processWindow = x11cb.read.window;
|
|
x11cb.read.propertyPending = false;
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (processProperty)
|
|
x11CBSelectionIncr((XPropertyEvent)
|
|
{
|
|
.display = x11.display,
|
|
.window = processWindow,
|
|
.atom = x11atoms.SEL_DATA,
|
|
.state = PropertyNewValue,
|
|
});
|
|
return;
|
|
}
|
|
|
|
LG_ClipboardData dataType;
|
|
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
|
if (x11cb.aTypes[dataType] == type)
|
|
break;
|
|
|
|
const LG_ClipboardRequest request = x11cb.read.request;
|
|
const bool valid = dataType != LG_CLIPBOARD_DATA_NONE &&
|
|
dataType == x11cb.read.type && format == 8 && (!itemCount || data);
|
|
cancelReadNL(false);
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
if (!valid)
|
|
{
|
|
DEBUG_WARN("Invalid clipboard data");
|
|
clipboard_abort(request);
|
|
goto out;
|
|
}
|
|
|
|
clipboard_data(request, dataType, data, itemCount);
|
|
|
|
out:
|
|
if (data)
|
|
XFree(data);
|
|
}
|
|
|
|
void x11CBNotice(LG_ClipboardData type)
|
|
{
|
|
const uint64_t nextPresentation = type == LG_CLIPBOARD_DATA_FILES ?
|
|
clipboardFiles_remotePresentationAcquire() : 0;
|
|
LG_LOCK(x11cb.lock);
|
|
const LG_ClipboardRequest oldRequest = x11cb.read.window ?
|
|
cancelReadNL(true) : LG_CLIPBOARD_REQUEST_INVALID;
|
|
clearTargetsNL();
|
|
clearFileImportNL();
|
|
const uint64_t oldPresentation = x11cb.filePresentation;
|
|
x11cb.filePresentation = nextPresentation;
|
|
x11cb.aCurSelection = BadValue;
|
|
x11cb.haveRequest = type != LG_CLIPBOARD_DATA_FILES || nextPresentation;
|
|
x11cb.type = type;
|
|
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD,
|
|
x11cb.haveRequest ? x11.window : None, CurrentTime);
|
|
XFlush(x11.display);
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
|
clipboard_abort(oldRequest);
|
|
if (oldPresentation)
|
|
clipboardFiles_remotePresentationRelease(oldPresentation);
|
|
clipboardFiles_clearLocal();
|
|
}
|
|
|
|
void x11CBRelease(void)
|
|
{
|
|
LG_LOCK(x11cb.lock);
|
|
x11cb.haveRequest = false;
|
|
const uint64_t presentation = x11cb.filePresentation;
|
|
x11cb.filePresentation = 0;
|
|
XGrabServer(x11.display);
|
|
if (XGetSelectionOwner(x11.display, x11atoms.CLIPBOARD) == x11.window)
|
|
XSetSelectionOwner(
|
|
x11.display, x11atoms.CLIPBOARD, None, CurrentTime);
|
|
XUngrabServer(x11.display);
|
|
XFlush(x11.display);
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (presentation)
|
|
clipboardFiles_remotePresentationRelease(presentation);
|
|
}
|
|
|
|
void x11CBFree(void)
|
|
{
|
|
x11CBRelease();
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
const LG_ClipboardRequest request = x11cb.read.window ?
|
|
cancelReadNL(true) : LG_CLIPBOARD_REQUEST_INVALID;
|
|
clearTargetsNL();
|
|
clearFileImportNL();
|
|
struct X11ClipboardWrite * writes = takeFileWritesNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
if (request != LG_CLIPBOARD_REQUEST_INVALID)
|
|
clipboard_abort(request);
|
|
cancelFileWrites(writes, true);
|
|
clipboardFiles_clearLocal();
|
|
}
|
|
|
|
void x11CBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
|
{
|
|
if (request == LG_CLIPBOARD_REQUEST_INVALID ||
|
|
type < LG_CLIPBOARD_DATA_TEXT || type >= LG_CLIPBOARD_DATA_NONE)
|
|
return;
|
|
|
|
LG_LOCK(x11cb.lock);
|
|
const LG_ClipboardRequest oldRequest = x11cb.read.window ?
|
|
cancelReadNL(true) : LG_CLIPBOARD_REQUEST_INVALID;
|
|
if (x11cb.aCurSelection == BadValue)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
|
clipboard_abort(oldRequest);
|
|
clipboard_abort(request);
|
|
return;
|
|
}
|
|
|
|
const Window window = XCreateSimpleWindow(
|
|
x11.display, x11.window, 0, 0, 1, 1, 0, 0, 0);
|
|
if (!window)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
|
clipboard_abort(oldRequest);
|
|
clipboard_abort(request);
|
|
return;
|
|
}
|
|
|
|
XSelectInput(x11.display, window, PropertyChangeMask);
|
|
x11cb.read = (struct X11ClipboardRead)
|
|
{
|
|
.window = window,
|
|
.request = request,
|
|
.type = type,
|
|
};
|
|
XConvertSelection(
|
|
x11.display,
|
|
x11cb.aCurSelection,
|
|
x11cb.aTypes[type],
|
|
x11atoms.SEL_DATA,
|
|
window,
|
|
CurrentTime);
|
|
XFlush(x11.display);
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
|
clipboard_abort(oldRequest);
|
|
}
|
|
|
|
void x11CBRequestReady(LG_ClipboardRequest request)
|
|
{
|
|
LG_LOCK(x11cb.lock);
|
|
if (x11cb.read.request != request)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
if (x11cb.read.calling)
|
|
{
|
|
x11cb.read.readyPending = true;
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
if (!x11cb.read.blocked)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
x11cb.read.blocked = false;
|
|
const bool begin = !x11cb.read.begun;
|
|
const bool end = x11cb.read.endPending;
|
|
const LG_ClipboardData type = x11cb.read.type;
|
|
const uint64_t sizeHint = x11cb.read.sizeHint;
|
|
const uint64_t offset = x11cb.read.offset;
|
|
const size_t pendingSize = x11cb.read.pendingSize;
|
|
const unsigned long pendingUnits = x11cb.read.pendingUnits;
|
|
const bool pendingMore = x11cb.read.pendingMore;
|
|
uint8_t * pending = NULL;
|
|
|
|
/* The provider call runs without x11cb.lock. Request cancellation or a
|
|
* selection replacement may therefore clear and free read.pending while
|
|
* the call is in progress. Give the call its own bounded copy so teardown
|
|
* can remain immediate without invalidating the callback's data. */
|
|
if (pendingSize)
|
|
{
|
|
pending = malloc(pendingSize);
|
|
if (!pending)
|
|
{
|
|
const LG_ClipboardRequest abort = cancelReadNL(true);
|
|
LG_UNLOCK(x11cb.lock);
|
|
clipboard_abort(abort);
|
|
return;
|
|
}
|
|
memcpy(pending, x11cb.read.pending, pendingSize);
|
|
}
|
|
|
|
enum X11ClipboardReadOperation operation;
|
|
if (begin)
|
|
operation = X11_CLIPBOARD_READ_BEGIN;
|
|
else if (pendingSize)
|
|
operation = X11_CLIPBOARD_READ_CHUNK;
|
|
else if (end)
|
|
operation = X11_CLIPBOARD_READ_END;
|
|
else
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
enum X11ClipboardReadCallResult result;
|
|
do
|
|
result = x11CBReadCallNL(operation, type, sizeHint,
|
|
offset, pending, pendingSize);
|
|
while (result == X11_CLIPBOARD_READ_CALL_RETRY);
|
|
free(pending);
|
|
if (result == X11_CLIPBOARD_READ_CALL_STALE)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
if (result == X11_CLIPBOARD_READ_CALL_BLOCKED)
|
|
{
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
if (result != X11_CLIPBOARD_READ_CALL_ACCEPTED)
|
|
{
|
|
cancelReadNL(false);
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
bool processProperty = false;
|
|
Window window = None;
|
|
if (begin)
|
|
x11cb.read.begun = true;
|
|
else if (pendingSize)
|
|
{
|
|
x11cb.read.offset += pendingSize;
|
|
free(x11cb.read.pending);
|
|
x11cb.read.pending = NULL;
|
|
x11cb.read.pendingSize = 0;
|
|
x11cb.read.pendingUnits = 0;
|
|
x11cb.read.pendingMore = false;
|
|
if (pendingMore)
|
|
{
|
|
if (!advanceReadPropertyNL(pendingUnits))
|
|
{
|
|
const struct X11ClipboardRead read = clearReadNL();
|
|
LG_UNLOCK(x11cb.lock);
|
|
free(read.pending);
|
|
clipboard_abort(read.request);
|
|
return;
|
|
}
|
|
processProperty = true;
|
|
window = x11cb.read.window;
|
|
}
|
|
else
|
|
x11cb.read.propertyOffset = 0;
|
|
}
|
|
else
|
|
{
|
|
cancelReadNL(false);
|
|
LG_UNLOCK(x11cb.lock);
|
|
return;
|
|
}
|
|
|
|
if (x11cb.read.propertyPending && x11cb.read.incremental &&
|
|
!x11cb.read.blocked && !x11cb.read.pendingSize &&
|
|
!x11cb.read.endPending)
|
|
{
|
|
x11cb.read.propertyPending = false;
|
|
processProperty = true;
|
|
window = x11cb.read.window;
|
|
}
|
|
LG_UNLOCK(x11cb.lock);
|
|
|
|
if (processProperty)
|
|
x11CBSelectionIncr((XPropertyEvent)
|
|
{
|
|
.display = x11.display,
|
|
.window = window,
|
|
.atom = x11atoms.SEL_DATA,
|
|
.state = PropertyNewValue,
|
|
});
|
|
}
|
|
|
|
void x11CBRequestCancel(LG_ClipboardRequest request,
|
|
LG_ClipboardCancelReason reason)
|
|
{
|
|
(void)reason;
|
|
LG_LOCK(x11cb.lock);
|
|
if (x11cb.read.request == request)
|
|
cancelReadNL(false);
|
|
LG_UNLOCK(x11cb.lock);
|
|
}
|