diff --git a/client/displayservers/X11/clipboard.c b/client/displayservers/X11/clipboard.c index e1ca07ac..747815fe 100644 --- a/client/displayservers/X11/clipboard.c +++ b/client/displayservers/X11/clipboard.c @@ -34,6 +34,9 @@ #include "common/debug.h" #include #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(); } diff --git a/client/displayservers/X11/clipboard.h b/client/displayservers/X11/clipboard.h index aa8e759e..cd9e10d0 100644 --- a/client/displayservers/X11/clipboard.h +++ b/client/displayservers/X11/clipboard.h @@ -22,11 +22,13 @@ #define _H_X11DS_CLIPBOARD_ #include +#include #include #include "interface/displayserver.h" bool x11CBEventThread(const XEvent * xe); +void x11CBMaintenance(uint64_t now); bool x11CBInit(void); void x11CBFree(void); diff --git a/client/displayservers/X11/x11.c b/client/displayservers/X11/x11.c index 017d9f6d..a97465bf 100644 --- a/client/displayservers/X11/x11.c +++ b/client/displayservers/X11/x11.c @@ -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); diff --git a/client/include/core/clipboard.h b/client/include/core/clipboard.h index 82812c1c..80ae17dd 100644 --- a/client/include/core/clipboard.h +++ b/client/include/core/clipboard.h @@ -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); diff --git a/client/include/interface/clipboard.h b/client/include/interface/clipboard.h index 336a5466..ac17b360 100644 --- a/client/include/interface/clipboard.h +++ b/client/include/interface/clipboard.h @@ -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); diff --git a/client/src/clipboard.c b/client/src/clipboard.c index a15edb3f..a07a73c2 100644 --- a/client/src/clipboard.c +++ b/client/src/clipboard.c @@ -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) { diff --git a/client/tests/x11_clipboard_test.c b/client/tests/x11_clipboard_test.c index 2e7a1619..790bc6a3 100644 --- a/client/tests/x11_clipboard_test.c +++ b/client/tests/x11_clipboard_test.c @@ -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) { diff --git a/client/transports/LGMP/clipboard.c b/client/transports/LGMP/clipboard.c index 41619e60..4112f1de 100644 --- a/client/transports/LGMP/clipboard.c +++ b/client/transports/LGMP/clipboard.c @@ -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, diff --git a/client/transports/SPICE/clipboard.c b/client/transports/SPICE/clipboard.c index 5d697ec1..6e0ef30f 100644 --- a/client/transports/SPICE/clipboard.c +++ b/client/transports/SPICE/clipboard.c @@ -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)