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

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

View File

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

View File

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

View File

@@ -53,6 +53,11 @@ bool clipboard_requestStream(LG_ClipboardData type,
const LG_ClipboardStreamOps * stream, void * opaque, const LG_ClipboardStreamOps * stream, void * opaque,
LG_ClipboardRequest * request); LG_ClipboardRequest * request);
bool clipboard_requestReady(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, bool clipboard_request(LG_ClipboardData type,
LG_ClipboardReplyFn replyFn, void * opaque); LG_ClipboardReplyFn replyFn, void * opaque);

View File

@@ -236,6 +236,10 @@ typedef struct LG_ClipboardOps
* or release first. */ * or release first. */
bool (*request)(void * opaque, LG_ClipboardRequest request, bool (*request)(void * opaque, LG_ClipboardRequest request,
LG_ClipboardData type); 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, bool (*fileAcquire)(void * opaque, uint64_t dataset,
uint64_t acquisition); uint64_t acquisition);

View File

@@ -2116,6 +2116,35 @@ bool clipboard_requestReady(LG_ClipboardRequest id)
return result; 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, bool clipboard_request(LG_ClipboardData type,
LG_ClipboardReplyFn replyFn, void * opaque) LG_ClipboardReplyFn replyFn, void * opaque)
{ {

View File

@@ -586,6 +586,14 @@ bool clipboard_requestReady(LG_ClipboardRequest request)
return true; 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, bool clipboardFiles_setLocal(const char * mime,
const void * data, size_t size) const void * data, size_t size)
{ {

View File

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

View File

@@ -284,6 +284,21 @@ static bool spiceRequest(void * opaque, LG_ClipboardRequest request,
return false; 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 = static const LG_ClipboardOps l_clipboardOps =
{ {
.name = "SPICE", .name = "SPICE",
@@ -294,6 +309,7 @@ static const LG_ClipboardOps l_clipboardOps =
.notifyTypes = spiceNotifyTypes, .notifyTypes = spiceNotifyTypes,
.data = spiceData, .data = spiceData,
.request = spiceRequest, .request = spiceRequest,
.requestCancel = spiceRequestCancel,
}; };
bool spiceClipboard_init(SpiceClipboard ** clipboard) bool spiceClipboard_init(SpiceClipboard ** clipboard)