Files
LookingGlass/client/displayservers/X11/clipboard.c
2026-08-14 19:09:11 +10:00

1813 lines
47 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;
};
struct X11ClipboardState
{
LG_Lock lock;
Atom aCurSelection;
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
Atom aFileGnome;
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 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.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_DATA_BYTES ?
KVMFR_CLIPBOARD_DATA_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)
lgClipboardFiles_remotePresentationDelivered(
write->filePresentation);
const uint64_t presentation = write->filePresentation;
writeRemoveNL(write);
LG_UNLOCK(x11cb.lock);
lgClipboardFiles_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)
lgClipboard_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.aFileKde = XInternAtom(x11.display, fileKdeMime, False);
if (x11cb.aFileGnome == BadAlloc || x11cb.aFileGnome == 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_DATA_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[4];
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.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 =
lgClipboardFiles_remotePresentationAcquire();
available = writePresentation == presentation &&
lgClipboardFiles_getRemotePresentation(writePresentation,
fileMime, &data, &size);
}
LG_UNLOCK(x11cb.lock);
if (!available)
{
free(data);
if (writePresentation)
lgClipboardFiles_remotePresentationRelease(writePresentation);
goto nodata;
}
struct X11ClipboardWrite * write = calloc(1, sizeof(*write));
if (!write)
{
free(data);
lgClipboardFiles_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);
lgClipboardFiles_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 = lgClipboard_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)
lgClipboardFiles_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);
lgClipboardFiles_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 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,
};
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 (!lgClipboardFiles_setLocal(import.mime, data, size))
{
lgClipboardFiles_clearLocal();
lgClipboard_release();
}
free(import.data);
}
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);
free(failed.data);
lgClipboard_release();
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);
free(failed.data);
lgClipboard_release();
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
{
free(import.data);
lgClipboard_release();
}
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_DATA_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);
free(failed.data);
lgClipboard_release();
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);
free(failed.data);
lgClipboard_release();
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 = lgClipboard_dataBegin(request, type, sizeHint);
break;
case X11_CLIPBOARD_READ_CHUNK:
result = lgClipboard_dataChunk(request, offset, data, size);
break;
case X11_CLIPBOARD_READ_END:
result = lgClipboard_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_DATA_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);
lgClipboard_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);
lgClipboard_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_DATA_BYTES)
{
const struct X11ClipboardRead read = clearReadNL();
LG_UNLOCK(x11cb.lock);
XFree(data);
free(read.pending);
lgClipboard_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);
lgClipboard_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);
lgClipboard_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)
lgClipboard_abort(oldRequest);
if (presentation)
lgClipboardFiles_remotePresentationRelease(presentation);
lgClipboardFiles_clearLocal();
lgClipboard_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)
lgClipboard_abort(oldRequest);
if (presentation)
lgClipboardFiles_remotePresentationRelease(presentation);
lgClipboardFiles_clearLocal();
lgClipboard_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)
lgClipboard_abort(oldRequest);
if (presentation)
lgClipboardFiles_remotePresentationRelease(presentation);
lgClipboardFiles_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);
lgClipboard_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);
lgClipboard_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)
{
lgClipboard_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;
Atom fileTarget = None;
for (unsigned long i = 0; i < itemCount; ++i)
if (targets[i] == x11cb.aFileGnome)
{
fileTarget = x11cb.aFileGnome;
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)
{
lgClipboardFiles_clearLocal();
if (!startFileImport(fileTarget))
lgClipboard_release();
goto out;
}
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
for(unsigned long i = 0; i < itemCount; ++i)
if (x11cb.aTypes[n] == targets[i])
{
types[typeCount++] = n;
break;
}
lgClipboard_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);
lgClipboard_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);
lgClipboard_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);
lgClipboard_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");
lgClipboard_abort(request);
goto out;
}
lgClipboard_data(request, dataType, data, itemCount);
out:
if (data)
XFree(data);
}
void x11CBNotice(LG_ClipboardData type)
{
const uint64_t nextPresentation = type == LG_CLIPBOARD_DATA_FILES ?
lgClipboardFiles_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)
lgClipboard_abort(oldRequest);
if (oldPresentation)
lgClipboardFiles_remotePresentationRelease(oldPresentation);
lgClipboardFiles_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)
lgClipboardFiles_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)
lgClipboard_abort(request);
cancelFileWrites(writes, true);
lgClipboardFiles_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)
lgClipboard_abort(oldRequest);
lgClipboard_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)
lgClipboard_abort(oldRequest);
lgClipboard_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)
lgClipboard_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);
lgClipboard_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);
lgClipboard_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);
}