[client] clipboard: keep controls responsive during stalls
Some checks are pending
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / module (push) Waiting to run
build / host-linux (push) Waiting to run
build / host-windows-cross (push) Waiting to run
build / host-windows-native (push) Waiting to run
build / idd (push) Waiting to run
build / obs (clang) (push) Waiting to run
build / obs (gcc) (push) Waiting to run
build / docs (push) Waiting to run

Release LGMP stream slots before dispatching clipboard data so an X11
INCR consumer cannot prevent control queue acknowledgements.

Retain one bounded record in client memory while the sink is blocked.
Cancel abandoned X11 transfers after requestor destruction or a finite
progress timeout.
This commit is contained in:
Geoffrey McRae
2026-10-08 09:58:04 +11:00
parent 6214dccf12
commit cdba29f2f6
9 changed files with 336 additions and 113 deletions

View File

@@ -34,6 +34,9 @@
#include "common/debug.h"
#include <LGProtocol/KVMFRClipboard.h>
#include "common/locking.h"
#include "common/time.h"
#define X11_CLIPBOARD_WRITE_TIMEOUT_US UINT64_C(30000000)
struct X11ClipboardRead
{
@@ -63,6 +66,7 @@ struct X11ClipboardWrite
LG_ClipboardRequest request;
LG_ClipboardData type;
uint64_t offset;
uint64_t progressAt;
bool begun;
bool blocked;
bool ready;
@@ -139,9 +143,10 @@ 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(
static struct X11ClipboardWrite * takeWritesNL(void);
static struct X11ClipboardWrite * takeWritesForWindowNL(Window window);
static struct X11ClipboardWrite * takeExpiredWritesNL(uint64_t now);
static void cancelWrites(
struct X11ClipboardWrite * writes, bool terminate);
static void x11CBFileImportIncr(const XPropertyEvent e);
@@ -187,11 +192,11 @@ bool x11CBEventThread(const XEvent * xe)
{
LG_LOCK(x11cb.lock);
struct X11ClipboardWrite * writes =
takeFileWritesForWindowNL(xe->xdestroywindow.window);
takeWritesForWindowNL(xe->xdestroywindow.window);
LG_UNLOCK(x11cb.lock);
if (!writes)
return false;
cancelFileWrites(writes, false);
cancelWrites(writes, false);
return true;
}
@@ -220,6 +225,7 @@ bool x11CBEventThread(const XEvent * xe)
break;
if (write)
{
write->progressAt = microtime();
if (write->file)
{
const size_t remaining = write->fileSize - (size_t)write->offset;
@@ -357,9 +363,10 @@ static LG_ClipboardResult x11CBWriteBegin(void * opaque,
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;
PropertyChangeMask | StructureNotifyMask);
write->progressAt = microtime();
write->begun = true;
write->ready = false;
XEvent event = write->event;
LG_UNLOCK(x11cb.lock);
x11CBWriteSend(&event);
@@ -394,9 +401,10 @@ static LG_ClipboardResult x11CBWriteChunk(void * opaque,
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;
write->progressAt = microtime();
write->ready = false;
write->blocked = false;
write->offset += size;
LG_UNLOCK(x11cb.lock);
XFlush(x11.display);
return LG_CLIPBOARD_RESULT_ACCEPTED;
@@ -578,6 +586,7 @@ static void x11CBSelectionRequest(const XSelectionRequestEvent e)
write->fileData = (uint8_t *)data;
write->fileSize = size;
write->filePresentation = writePresentation;
write->progressAt = microtime();
write->begun = true;
LG_LOCK(x11cb.lock);
@@ -632,10 +641,11 @@ static void x11CBSelectionRequest(const XSelectionRequestEvent e)
DEBUG_ERROR("out of memory");
goto nodata;
}
write->event = *s;
write->request = LG_CLIPBOARD_REQUEST_INVALID;
write->type = requestType;
write->creating = true;
write->event = *s;
write->request = LG_CLIPBOARD_REQUEST_INVALID;
write->type = requestType;
write->creating = true;
write->progressAt = microtime();
LG_LOCK(x11cb.lock);
bool duplicate = false;
@@ -649,8 +659,11 @@ static void x11CBSelectionRequest(const XSelectionRequestEvent e)
}
if (!duplicate)
{
write->next = x11cb.writes;
write->next = x11cb.writes;
x11cb.writes = write;
XSelectInput(x11.display, e.requestor,
PropertyChangeMask | StructureNotifyMask);
XFlush(x11.display);
}
LG_UNLOCK(x11cb.lock);
if (duplicate)
@@ -796,49 +809,57 @@ static void clearFileImportNL(void)
}
/* x11cb.lock must be held. */
static struct X11ClipboardWrite * takeFileWritesNL(void)
static struct X11ClipboardWrite * takeWritesNL(void)
{
struct X11ClipboardWrite * result = x11cb.writes;
x11cb.writes = NULL;
return result;
}
/* x11cb.lock must be held. */
static struct X11ClipboardWrite * takeWritesForWindowNL(Window window)
{
struct X11ClipboardWrite * result = NULL;
struct X11ClipboardWrite ** link = &x11cb.writes;
struct X11ClipboardWrite ** link = &x11cb.writes;
while (*link)
{
struct X11ClipboardWrite * write = *link;
if (!write->file)
if (write->event.xselection.requestor != window)
{
link = &write->next;
continue;
}
*link = write->next;
*link = write->next;
write->next = result;
result = write;
result = write;
}
return result;
}
/* x11cb.lock must be held. */
static struct X11ClipboardWrite * takeFileWritesForWindowNL(Window window)
static struct X11ClipboardWrite * takeExpiredWritesNL(uint64_t now)
{
struct X11ClipboardWrite * result = NULL;
struct X11ClipboardWrite ** link = &x11cb.writes;
struct X11ClipboardWrite ** link = &x11cb.writes;
while (*link)
{
struct X11ClipboardWrite * write = *link;
if (!write->file ||
write->event.xselection.requestor != window)
if (write->file || write->creating || now < write->progressAt ||
now - write->progressAt < X11_CLIPBOARD_WRITE_TIMEOUT_US)
{
link = &write->next;
continue;
}
*link = write->next;
*link = write->next;
write->next = result;
result = write;
result = write;
}
return result;
}
static void cancelFileWrites(
static void cancelWrites(
struct X11ClipboardWrite * writes, bool terminate)
{
const bool flush = terminate && writes;
@@ -846,19 +867,51 @@ static void cancelFileWrites(
{
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);
{
if (writes->begun)
XChangeProperty(x11.display, writes->event.xselection.requestor,
writes->event.xselection.property,
writes->event.xselection.target, 8, PropModeReplace, NULL, 0);
else
{
XEvent reply = writes->event;
reply.xselection.property = None;
XSendEvent(x11.display, reply.xselection.requestor, 0, 0, &reply);
}
}
const LG_ClipboardRequest request = writes->request;
if (writes->file)
{
clipboardFiles_remotePresentationRelease(
writes->filePresentation);
free(writes->fileData);
free(writes);
}
else
{
if (request != LG_CLIPBOARD_REQUEST_INVALID)
clipboard_requestCancel(request, LG_CLIPBOARD_CANCEL_UNAVAILABLE);
free(writes);
}
writes = next;
}
if (flush)
XFlush(x11.display);
}
void x11CBMaintenance(uint64_t now)
{
LG_LOCK(x11cb.lock);
struct X11ClipboardWrite * writes = takeExpiredWritesNL(now);
LG_UNLOCK(x11cb.lock);
if (!writes)
return;
DEBUG_WARN("Cancelling stalled X11 clipboard transfer");
cancelWrites(writes, true);
}
static bool fileImportGrowNL(size_t wanted)
{
if (wanted <= x11cb.fileImport.capacity)
@@ -1652,12 +1705,12 @@ void x11CBFree(void)
cancelReadNL(true) : LG_CLIPBOARD_REQUEST_INVALID;
clearTargetsNL();
clearFileImportNL();
struct X11ClipboardWrite * writes = takeFileWritesNL();
struct X11ClipboardWrite * writes = takeWritesNL();
LG_UNLOCK(x11cb.lock);
if (request != LG_CLIPBOARD_REQUEST_INVALID)
clipboard_abort(request);
cancelFileWrites(writes, true);
cancelWrites(writes, true);
clipboardFiles_clearLocal();
}

View File

@@ -22,11 +22,13 @@
#define _H_X11DS_CLIPBOARD_
#include <stdbool.h>
#include <stdint.h>
#include <X11/extensions/Xfixes.h>
#include "interface/displayserver.h"
bool x11CBEventThread(const XEvent * xe);
void x11CBMaintenance(uint64_t now);
bool x11CBInit(void);
void x11CBFree(void);

View File

@@ -1133,7 +1133,10 @@ static int x11EventThread(void * unused)
x11.eventSource.callback(x11.eventSource.opaque);
if (!XPending(x11.display))
{
x11CBMaintenance(microtime());
continue;
}
XEvent xe;
XNextEvent(x11.display, &xe);

View File

@@ -53,6 +53,11 @@ bool clipboard_requestStream(LG_ClipboardData type,
const LG_ClipboardStreamOps * stream, void * opaque,
LG_ClipboardRequest * request);
bool clipboard_requestReady(LG_ClipboardRequest request);
/* The consumer must unlink its stream state before cancellation, but retain
* its opaque storage until this returns. Success means the local request was
* canceled; delivery to the provider is best effort. */
bool clipboard_requestCancel(LG_ClipboardRequest request,
LG_ClipboardCancelReason reason);
bool clipboard_request(LG_ClipboardData type,
LG_ClipboardReplyFn replyFn, void * opaque);

View File

@@ -236,6 +236,10 @@ typedef struct LG_ClipboardOps
* or release first. */
bool (*request)(void * opaque, LG_ClipboardRequest request,
LG_ClipboardData type);
/* Best-effort cancellation of a request previously submitted through
* request(). The consumer has already made its stream state inactive. */
bool (*requestCancel)(void * opaque, LG_ClipboardRequest request,
LG_ClipboardCancelReason reason);
bool (*fileAcquire)(void * opaque, uint64_t dataset,
uint64_t acquisition);

View File

@@ -2116,6 +2116,35 @@ bool clipboard_requestReady(LG_ClipboardRequest id)
return result;
}
bool clipboard_requestCancel(LG_ClipboardRequest id,
LG_ClipboardCancelReason reason)
{
if (id == LG_CLIPBOARD_REQUEST_INVALID ||
(unsigned int)reason > LG_CLIPBOARD_CANCEL_INVALID ||
!clipboard.requests)
return false;
LG_LOCK(clipboard.callbackLock);
LG_LOCK(clipboard.requestLock);
ClipboardRequest * request = takeRequest(id);
LG_UNLOCK(clipboard.requestLock);
if (!request)
{
LG_UNLOCK(clipboard.callbackLock);
return false;
}
const ClipboardBinding binding = request->binding;
freeRequest(request);
LG_LOCK_SHARED(clipboard.activeLock);
if (bindingActiveNL(&binding) && binding.ops->requestCancel)
binding.ops->requestCancel(binding.opaque, id, reason);
LG_UNLOCK_SHARED(clipboard.activeLock);
LG_UNLOCK(clipboard.callbackLock);
return true;
}
bool clipboard_request(LG_ClipboardData type,
LG_ClipboardReplyFn replyFn, void * opaque)
{

View File

@@ -586,6 +586,14 @@ bool clipboard_requestReady(LG_ClipboardRequest request)
return true;
}
bool clipboard_requestCancel(LG_ClipboardRequest request,
LG_ClipboardCancelReason reason)
{
CHECK(request == rec.requestId);
(void)reason;
return true;
}
bool clipboardFiles_setLocal(const char * mime,
const void * data, size_t size)
{

View File

@@ -126,10 +126,11 @@ struct LGMPClipboard
bool held;
bool heldReady;
enum HeldPhase heldPhase;
LGMPStreamBuffer heldStreamBuffer;
KVMFRClipboardMessage heldRecord;
uint64_t heldWireTransfer;
bool heldFile;
LG_ClipboardFileRequest heldFileRequest;
uint8_t heldData[KVMFR_CLIPBOARD_DATA_BYTES];
uint64_t localClipboardGeneration;
uint64_t localGenerationSerial;
@@ -257,12 +258,22 @@ static LGMP_STATUS activateStreamsNL(LGMPClipboard * clipboard)
if (!clipboard->hostToClientStream || !clipboard->clientToHostStream)
return LGMP_ERR_STREAM_UNBOUND;
LGMP_STATUS status = lgmpClientStreamActivate(
clipboard->hostToClientStream, NULL);
uint32_t clientID;
uint32_t epoch;
LGMP_STATUS status = lgmpClientStreamGetBinding(
clipboard->hostToClientStream, &clientID, &epoch);
if (status == LGMP_ERR_STREAM_UNBOUND || status == LGMP_ERR_STREAM_STALE)
status = lgmpClientStreamActivate(clipboard->hostToClientStream, NULL);
if (status != LGMP_OK)
return status;
return lgmpClientStreamActivate(
clipboard->clientToHostStream, NULL);
status = lgmpClientStreamGetBinding(
clipboard->clientToHostStream, &clientID, &epoch);
if (status == LGMP_OK)
return LGMP_OK;
if (status != LGMP_ERR_STREAM_UNBOUND && status != LGMP_ERR_STREAM_STALE)
return status;
return lgmpClientStreamActivate(clipboard->clientToHostStream, NULL);
}
static LG_ClipboardData fromWireFormat(KVMFRClipboardFormat format)
@@ -768,6 +779,39 @@ static void clearDataPlaneNL(LGMPClipboard * clipboard)
clearFilesNL(clipboard);
}
static void clearHeldNL(LGMPClipboard * clipboard)
{
clipboard->held = false;
clipboard->heldReady = false;
clipboard->heldPhase = HELD_PHASE_NONE;
clipboard->heldWireTransfer = 0;
clipboard->heldFile = false;
memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord));
memset(&clipboard->heldFileRequest, 0,
sizeof(clipboard->heldFileRequest));
}
static bool heldValidNL(const LGMPClipboard * clipboard)
{
if (!clipboard->held)
return false;
if (clipboard->heldFile)
{
const struct FileTransfer * transfer = fileTransferFindNL(
clipboard->fileReads, clipboard->heldWireTransfer);
return transfer &&
transfer->request.dataset == clipboard->heldFileRequest.dataset &&
transfer->request.request == clipboard->heldFileRequest.request;
}
return clipboard->readRequest == clipboard->heldRecord.transfer &&
clipboard->readTransfer == clipboard->heldWireTransfer &&
clipboard->readClipboardGeneration ==
clipboard->heldRecord.clipboardGeneration &&
clipboard->readFormat == clipboard->heldRecord.format;
}
static void clearProtocolNL(LGMPClipboard * clipboard)
{
clipboard->claimed = false;
@@ -843,14 +887,7 @@ static void connectionLostNL(LGMPClipboard * clipboard)
clipboard->statusValid = false;
detachStreamsNL(clipboard);
clearProtocolNL(clipboard);
clipboard->held = false;
clipboard->heldReady = false;
memset(&clipboard->heldStreamBuffer, 0,
sizeof(clipboard->heldStreamBuffer));
memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord));
clipboard->heldFile = false;
memset(&clipboard->heldFileRequest, 0,
sizeof(clipboard->heldFileRequest));
clearHeldNL(clipboard);
if (changed)
nextNonzero(&clipboard->providerGeneration);
atomic_store_explicit(&clipboard->stop, true, memory_order_release);
@@ -1522,11 +1559,11 @@ static bool processHeld(LGMPClipboard * clipboard)
{
for (;;)
{
KVMFRClipboardMessage record;
const uint8_t * data;
enum HeldPhase phase;
bool file;
LGMPStreamBuffer streamBuffer;
KVMFRClipboardMessage record;
const uint8_t * data;
enum HeldPhase phase;
bool file;
uint64_t wireTransfer;
LG_ClipboardFileRequest fileRequest;
LG_LOCK(clipboard->lock);
if (!clipboard->held || !clipboard->heldReady)
@@ -1534,13 +1571,13 @@ static bool processHeld(LGMPClipboard * clipboard)
LG_UNLOCK(clipboard->lock);
return true;
}
record = clipboard->heldRecord;
streamBuffer = clipboard->heldStreamBuffer;
data = !record.length ? NULL :
(const uint8_t *)streamBuffer.data + sizeof(record);
phase = clipboard->heldPhase;
file = clipboard->heldFile;
fileRequest = clipboard->heldFileRequest;
record = clipboard->heldRecord;
data = record.length ? clipboard->heldData : NULL;
phase = clipboard->heldPhase;
file = clipboard->heldFile;
wireTransfer = clipboard->heldWireTransfer;
fileRequest = clipboard->heldFileRequest;
clipboard->heldReady = false;
LG_UNLOCK(clipboard->lock);
@@ -1595,9 +1632,9 @@ static bool processHeld(LGMPClipboard * clipboard)
const bool matchingRead = !file &&
clipboard->readRequest == record.transfer &&
clipboard->readTransfer;
clipboard->readTransfer == wireTransfer;
struct FileTransfer * matchingFile = file ? fileTransferFindNL(
clipboard->fileReads, record.transfer) : NULL;
clipboard->fileReads, wireTransfer) : NULL;
if (result == LG_CLIPBOARD_RESULT_FAILED && (matchingRead || matchingFile))
{
KVMFRClipboardMessage cancel = { 0 };
@@ -1619,27 +1656,13 @@ static bool processHeld(LGMPClipboard * clipboard)
if (matchingFile && result == LG_CLIPBOARD_RESULT_ACCEPTED)
advanceFileReadNL(clipboard, &record);
else if (matchingFile)
free(fileTransferTakeNL(&clipboard->fileReads, record.transfer));
free(fileTransferTakeNL(&clipboard->fileReads, wireTransfer));
else if (matchingRead && result == LG_CLIPBOARD_RESULT_ACCEPTED)
advanceReadNL(clipboard, &record);
else if (matchingRead)
clearReadNL(clipboard);
const LGMP_STATUS done = lgmpClientStreamReadRelease(
clipboard->hostToClientStream, &streamBuffer);
clipboard->held = false;
memset(&clipboard->heldStreamBuffer, 0,
sizeof(clipboard->heldStreamBuffer));
memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord));
clipboard->heldFile = false;
memset(&clipboard->heldFileRequest, 0,
sizeof(clipboard->heldFileRequest));
if (done != LGMP_OK)
{
connectionFailed(clipboard, done);
LG_UNLOCK(clipboard->lock);
return false;
}
clearHeldNL(clipboard);
LG_UNLOCK(clipboard->lock);
return true;
}
@@ -1736,17 +1759,30 @@ static bool processStream(LGMPClipboard * clipboard, bool * processed)
return status == LGMP_OK;
}
const uint64_t wireTransfer = record.transfer;
if (record.length)
memcpy(clipboard->heldData,
(const uint8_t *)buffer.data + sizeof(record), record.length);
if (!file)
record.transfer = clipboard->readRequest;
clipboard->held = true;
clipboard->heldReady = true;
clipboard->heldStreamBuffer = buffer;
clipboard->heldRecord = record;
clipboard->heldWireTransfer = wireTransfer;
clipboard->heldFile = file;
clipboard->heldFileRequest = fileRequest;
clipboard->heldPhase =
(record.flags & KVMFR_CLIPBOARD_FLAG_BEGIN) ? HELD_PHASE_BEGIN :
record.length ? HELD_PHASE_CHUNK : HELD_PHASE_END;
status = lgmpClientStreamReadRelease(
clipboard->hostToClientStream, &buffer);
if (status != LGMP_OK)
{
clearHeldNL(clipboard);
connectionFailed(clipboard, status);
LG_UNLOCK(clipboard->lock);
return false;
}
LG_UNLOCK(clipboard->lock);
return processHeld(clipboard);
}
@@ -1755,7 +1791,7 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
{
*processed = false;
LG_LOCK(clipboard->lock);
if (!clipboard->connected || !clipboard->queue || clipboard->held)
if (!clipboard->connected || !clipboard->queue)
{
LG_UNLOCK(clipboard->lock);
return true;
@@ -1780,18 +1816,29 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
KVMFR_CLIPBOARD_QUEUE_TYPE(message.udata);
if (type == KVMFR_CLIPBOARD_QUEUE_STATUS)
{
KVMFRClipboardStatus snapshot = { 0 };
const bool valid = message.size == sizeof(snapshot);
KVMFRClipboardStatus snapshot = { 0 };
const bool valid = message.size == sizeof(snapshot);
const bool hadHeld = clipboard->held;
const LG_ClipboardRequest heldRequest =
clipboard->held && !clipboard->heldFile ?
clipboard->heldRecord.transfer : LG_CLIPBOARD_REQUEST_INVALID;
if (valid)
memcpy(&snapshot, message.mem, sizeof(snapshot));
status = lgmpClientMessageDone(clipboard->queue);
bool changed = false;
bool changed = false;
LG_ClipboardRequest cancelledRead = LG_CLIPBOARD_REQUEST_INVALID;
if (status == LGMP_OK && valid && validStatus(&snapshot))
{
const uint32_t serial = KVMFR_CLIPBOARD_QUEUE_SERIAL(message.udata);
if (!clipboard->statusValid ||
(int32_t)(serial - clipboard->statusSerial) > 0)
applyStatusNL(clipboard, &snapshot, serial, &changed);
if (hadHeld && !heldValidNL(clipboard))
{
if (heldRequest != LG_CLIPBOARD_REQUEST_INVALID)
cancelledRead = heldRequest;
clearHeldNL(clipboard);
}
}
else if (status == LGMP_OK)
DEBUG_WARN("Ignoring invalid LGMP clipboard status");
@@ -1799,6 +1846,9 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
connectionFailed(clipboard, status);
LG_UNLOCK(clipboard->lock);
dispatchRetiredFiles(clipboard);
if (cancelledRead != LG_CLIPBOARD_REQUEST_INVALID)
dispatchDataCancel(clipboard, cancelledRead,
LG_CLIPBOARD_CANCEL_UNAVAILABLE);
if (changed)
notifyStatus(clipboard);
return status == LGMP_OK;
@@ -1852,21 +1902,38 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
LG_UNLOCK(clipboard->lock);
return false;
}
bool dispatch = true;
bool fileRejected = false;
LG_ClipboardRequest cancelledRead = LG_CLIPBOARD_REQUEST_INVALID;
LG_ClipboardFileRequest fileRequest = { 0 };
bool dispatch = true;
bool fileRejected = false;
LG_ClipboardRequest cancelledRead =
LG_CLIPBOARD_REQUEST_INVALID;
LG_ClipboardCancelReason cancelledReadReason =
LG_CLIPBOARD_CANCEL_INVALID;
LG_ClipboardFileRequest fileRequest = { 0 };
switch (record.type)
{
case KVMFR_CLIPBOARD_MESSAGE_OFFER:
if (clipboard->readRequest != LG_CLIPBOARD_REQUEST_INVALID)
{
cancelledRead = clipboard->readRequest;
cancelledReadReason = LG_CLIPBOARD_CANCEL_REPLACED;
}
if (clipboard->held && !clipboard->heldFile)
clearHeldNL(clipboard);
clipboard->remoteClipboardGeneration = record.clipboardGeneration;
clipboard->remoteFormats = record.token;
clipboard->remoteFormats = record.token;
clearReadNL(clipboard);
break;
case KVMFR_CLIPBOARD_MESSAGE_CLEAR:
if (clipboard->readRequest != LG_CLIPBOARD_REQUEST_INVALID)
{
cancelledRead = clipboard->readRequest;
cancelledReadReason = LG_CLIPBOARD_CANCEL_UNAVAILABLE;
}
if (clipboard->held && !clipboard->heldFile)
clearHeldNL(clipboard);
clipboard->remoteClipboardGeneration = record.clipboardGeneration;
clipboard->remoteFormats = 0;
clipboard->remoteFormats = 0;
clearReadNL(clipboard);
break;
@@ -1889,7 +1956,7 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
case KVMFR_CLIPBOARD_MESSAGE_CANCEL:
{
const bool write = clipboard->writeTransfer == record.transfer;
const bool read = clipboard->readTransfer == record.transfer;
const bool read = clipboard->readTransfer == record.transfer;
if (!write && !read)
{
dispatch = false;
@@ -1911,7 +1978,11 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
clearWriteNL(clipboard);
if (read)
{
cancelledRead = clipboard->readRequest;
cancelledRead = clipboard->readRequest;
cancelledReadReason = fromWireCancel(record.token);
if (clipboard->held && !clipboard->heldFile &&
clipboard->heldWireTransfer == record.transfer)
clearHeldNL(clipboard);
clearReadNL(clipboard);
}
break;
@@ -2077,6 +2148,11 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
dispatch = false;
break;
}
if (clipboard->held && clipboard->heldFile &&
clipboard->heldWireTransfer == record.transfer &&
clipboard->heldFileRequest.dataset ==
record.clipboardGeneration)
clearHeldNL(clipboard);
free(transfer);
free(acquisition);
break;
@@ -2093,6 +2169,10 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
return true;
}
if (cancelledRead != LG_CLIPBOARD_REQUEST_INVALID)
dispatchDataCancel(
clipboard, cancelledRead, cancelledReadReason);
switch (record.type)
{
case KVMFR_CLIPBOARD_MESSAGE_OFFER:
@@ -2118,9 +2198,7 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
}
break;
case KVMFR_CLIPBOARD_MESSAGE_CANCEL:
if (cancelledRead != LG_CLIPBOARD_REQUEST_INVALID)
dispatchDataCancel(clipboard, cancelledRead, record.token);
else
if (cancelledRead == LG_CLIPBOARD_REQUEST_INVALID)
dispatchCancel(clipboard, &record);
break;
case KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE:
@@ -2368,8 +2446,7 @@ bool lgmpClipboard_connect(LGMPClipboard * clipboard, uint32_t clientID)
clipboard->statusSerial = 0;
clipboard->pendingHead = 0;
clipboard->pendingCount = 0;
clipboard->held = false;
clipboard->heldReady = false;
clearHeldNL(clipboard);
clearWriteNL(clipboard);
clipboard->remoteFormats = 0;
clipboard->remoteClipboardGeneration = 0;
@@ -2473,23 +2550,12 @@ void lgmpClipboard_disconnect(LGMPClipboard * clipboard)
lgJoinThread(thread, NULL);
LG_LOCK(clipboard->lock);
if (clipboard->held)
{
const LGMP_STATUS status = lgmpClientStreamReadRelease(
clipboard->hostToClientStream, &clipboard->heldStreamBuffer);
if (status != LGMP_OK && status != LGMP_ERR_STREAM_STALE &&
status != LGMP_ERR_STREAM_UNBOUND)
DEBUG_WARN("Failed to release held clipboard data during "
"disconnect: %s", lgmpStatusString(status));
}
releaseOnDisconnect(clipboard);
PLGMPClientQueue queue = clipboard->queue;
clipboard->queue = NULL;
clipboard->thread = NULL;
clipboard->event = NULL;
clipboard->held = false;
memset(&clipboard->heldStreamBuffer, 0,
sizeof(clipboard->heldStreamBuffer));
clearHeldNL(clipboard);
detachStreamsNL(clipboard);
clearProtocolNL(clipboard);
LG_UNLOCK(clipboard->lock);
@@ -2580,8 +2646,7 @@ static void detach(void * opaque)
clipboard->available = false;
}
clearDataPlaneNL(clipboard);
if (clipboard->held)
clipboard->heldReady = true;
clearHeldNL(clipboard);
LG_UNLOCK(clipboard->lock);
LG_UNLOCK(clipboard->eventLock);
if (!releaseQueued && releaseRequired)
@@ -2845,6 +2910,39 @@ static bool requestData(void * opaque, LG_ClipboardRequest request,
return result;
}
static bool requestCancel(void * opaque, LG_ClipboardRequest request,
LG_ClipboardCancelReason reason)
{
LGMPClipboard * clipboard = opaque;
if (request == LG_CLIPBOARD_REQUEST_INVALID ||
(unsigned)reason > LG_CLIPBOARD_CANCEL_INVALID)
return false;
LG_LOCK(clipboard->lock);
const bool matches = clipboard->readRequest == request &&
clipboard->readTransfer != 0;
KVMFRClipboardMessage record = { 0 };
record.type = KVMFR_CLIPBOARD_MESSAGE_CANCEL;
record.clipboardGeneration = clipboard->readClipboardGeneration;
record.transfer = clipboard->readTransfer;
record.format = clipboard->readFormat;
record.token = reason;
const bool result = matches && clipboard->claimed &&
enqueueUrgentRecordNL(clipboard, record);
if (matches)
{
if (clipboard->held && !clipboard->heldFile &&
clipboard->heldRecord.transfer == request &&
clipboard->heldWireTransfer == clipboard->readTransfer)
clearHeldNL(clipboard);
clearReadNL(clipboard);
}
LG_UNLOCK(clipboard->lock);
if (result)
signalWorker(clipboard);
return result;
}
static bool offerFiles(void * opaque, uint64_t dataset)
{
LGMPClipboard * clipboard = opaque;
@@ -3190,8 +3288,12 @@ static bool fileCancel(void * opaque, uint64_t dataset,
const bool matches = (read && read->request.dataset == dataset) ||
(write && write->request.dataset == dataset) || acquisition;
const bool result = matches && enqueueUrgentRecordNL(clipboard, record);
if (result)
if (matches)
{
if (clipboard->held && clipboard->heldFile &&
clipboard->heldWireTransfer == request &&
clipboard->heldFileRequest.dataset == dataset)
clearHeldNL(clipboard);
free(fileTransferTakeNL(&clipboard->fileReads, request));
free(fileTransferTakeNL(&clipboard->fileWrites, request));
if (acquisition)
@@ -3219,6 +3321,7 @@ static const LG_ClipboardOps CLIPBOARD_OPS =
.dataCancel = dataCancel,
.dataReady = dataReady,
.request = requestData,
.requestCancel = requestCancel,
.fileAcquire = fileAcquire,
.fileAcquired = fileAcquired,
.fileRelease = fileRelease,

View File

@@ -284,6 +284,21 @@ static bool spiceRequest(void * opaque, LG_ClipboardRequest request,
return false;
}
static bool spiceRequestCancel(void * opaque,
LG_ClipboardRequest request, LG_ClipboardCancelReason reason)
{
SpiceClipboard * clipboard = opaque;
(void)reason;
LG_LOCK(clipboard->stateLock);
const bool valid = clipboard->read.pending &&
clipboard->read.request == request;
if (valid)
clipboard->read = (PendingRequest) { 0 };
LG_UNLOCK(clipboard->stateLock);
return valid;
}
static const LG_ClipboardOps l_clipboardOps =
{
.name = "SPICE",
@@ -294,6 +309,7 @@ static const LG_ClipboardOps l_clipboardOps =
.notifyTypes = spiceNotifyTypes,
.data = spiceData,
.request = spiceRequest,
.requestCancel = spiceRequestCancel,
};
bool spiceClipboard_init(SpiceClipboard ** clipboard)