From 057073924abe028029554b080820d98499d60508 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Tue, 25 Aug 2026 22:22:11 +1000 Subject: [PATCH] [idd] clipboard: handle delayed-render sequence changes --- idd/LGIddHelper/CClipboardManager.cpp | 112 ++++++++++++++++++++++---- idd/LGIddHelper/CClipboardManager.h | 22 ++--- 2 files changed, 108 insertions(+), 26 deletions(-) diff --git a/idd/LGIddHelper/CClipboardManager.cpp b/idd/LGIddHelper/CClipboardManager.cpp index 2b75ecd8..41f8bb54 100644 --- a/idd/LGIddHelper/CClipboardManager.cpp +++ b/idd/LGIddHelper/CClipboardManager.cpp @@ -3404,7 +3404,8 @@ void CClipboardManager::HandleRequest( record.clipboardGeneration != m_localGeneration || record.clipboardGeneration != Atomic::Load( m_liveLocalGeneration, std::memory_order_acquire) || - GetClipboardSequenceNumber() != m_localSequence) + GetClipboardSequenceNumber() != m_localSequence || + GetClipboardOwner() != m_localOwner) { QueueCancel(record, ERROR_NOT_FOUND); return; @@ -3418,8 +3419,11 @@ void CClipboardManager::HandleRequest( return; } - std::shared_ptr spool = - CaptureFormat(record.format, m_localSequence); + const DWORD sequence = m_localSequence; + const HWND owner = m_localOwner; + DWORD capturedSequence = sequence; + std::shared_ptr spool = CaptureFormat( + record.format, sequence, owner, capturedSequence); if (!spool) { const DWORD error = GetLastError(); @@ -3428,6 +3432,38 @@ void CClipboardManager::HandleRequest( return; } + bool generationLive = false; + bool transferLive = false; + { + std::lock_guard lock(m_outgoingLock); + generationLive = + record.clipboardGeneration == Atomic::Load( + m_liveLocalGeneration, std::memory_order_acquire); + transferLive = generationLive && + record.transfer == Atomic::Load( + m_outgoingTransfer, std::memory_order_acquire); + if (generationLive && capturedSequence != sequence) + { + m_localSequence = capturedSequence; + if (capturedSequence) + { + m_materializedSequence = capturedSequence; + m_materializedGeneration = record.clipboardGeneration; + } + else + { + m_materializedSequence = 0; + m_materializedGeneration = 0; + } + } + } + if (!transferLive) + { + ReleaseOutgoing(record.transfer); + QueueCancel(record, ERROR_OPERATION_ABORTED); + return; + } + Work data; data.type = WorkType::SEND_DATA; data.record = record; @@ -3970,6 +4006,23 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount, void CClipboardManager::HandleClipboardUpdate() { + const DWORD sequenceBefore = GetClipboardSequenceNumber(); + const HWND owner = GetClipboardOwner(); + const DWORD sequenceAfter = GetClipboardSequenceNumber(); + // GetClipboardData can materialize delayed data and post an update without + // changing the clipboard that the current generation represents. + if (sequenceBefore && + sequenceBefore == sequenceAfter && + sequenceBefore == m_materializedSequence && + owner == m_localOwner && + m_materializedGeneration && + m_materializedGeneration == Atomic::Load( + m_liveLocalGeneration, std::memory_order_acquire)) + return; + + m_materializedSequence = 0; + m_materializedGeneration = 0; + if (m_applyingRemote || IsOurClipboard()) { ClearLocalRetry(); @@ -4024,17 +4077,22 @@ void CClipboardManager::PublishLocalClipboard() ClearLocalRetry(); Atomic::Store(m_liveLocalGeneration, UINT64_C(0), std::memory_order_release); - m_localSequence = GetClipboardSequenceNumber(); + m_localSequence = GetClipboardSequenceNumber(); + m_localOwner = GetClipboardOwner(); + m_materializedSequence = 0; + m_materializedGeneration = 0; return; } - const DWORD before = GetClipboardSequenceNumber(); - uint32_t formats = EnumerateFormats(); + const DWORD before = GetClipboardSequenceNumber(); + const HWND ownerBefore = GetClipboardOwner(); + uint32_t formats = EnumerateFormats(); const uint32_t recognizedFormats = formats; - const int rawFormatCount = CountClipboardFormatsLogged( + const int rawFormatCount = CountClipboardFormatsLogged( "PublishLocalClipboard", before); - const DWORD after = GetClipboardSequenceNumber(); - if (before != after) + const DWORD after = GetClipboardSequenceNumber(); + const HWND ownerAfter = GetClipboardOwner(); + if (before != after || ownerBefore != ownerAfter) { ClearLocalRetry(); PostMessageW(m_hwnd, WM_CLIPBOARDUPDATE, 0, 0); @@ -4050,7 +4108,8 @@ void CClipboardManager::PublishLocalClipboard() after, rawFormatCount, fileViaOLE, fileRetryStage, fileError, fileOleError); const DWORD capturedSequence = GetClipboardSequenceNumber(); - if (capturedSequence != after) + const HWND capturedOwner = GetClipboardOwner(); + if (capturedSequence != after || capturedOwner != ownerAfter) { DEBUG_ERROR_HR(HRESULT_FROM_WIN32(ERROR_RETRY), "Failed to publish local clipboard: " @@ -4071,7 +4130,10 @@ void CClipboardManager::PublishLocalClipboard() uint64_t generation = ++m_localGeneration; if (!generation) generation = ++m_localGeneration; - m_localSequence = after; + m_localSequence = after; + m_localOwner = ownerAfter; + m_materializedSequence = 0; + m_materializedGeneration = 0; if (files) { formats |= KVMFR_CLIPBOARD_FORMAT_MASK_FILES; @@ -4182,7 +4244,10 @@ void CClipboardManager::InvalidateOutgoing(uint32_t reason) void CClipboardManager::InvalidateLocalClipboard(uint32_t reason) { - m_localSequence = 0; + m_localSequence = 0; + m_localOwner = nullptr; + m_materializedSequence = 0; + m_materializedGeneration = 0; RetireLocalFileDataset(); InvalidateOutgoing(reason); } @@ -4236,7 +4301,10 @@ void CClipboardManager::ExpireLocalRetry(DWORD sequence) uint64_t generation = ++m_localGeneration; if (!generation) generation = ++m_localGeneration; - m_localSequence = sequence; + m_localSequence = sequence; + m_localOwner = GetClipboardOwner(); + m_materializedSequence = 0; + m_materializedGeneration = 0; PublishClear(generation); } @@ -4472,9 +4540,12 @@ UINT CClipboardManager::ToWindowsFormat(KVMFRClipboardFormat format) const } std::shared_ptr CClipboardManager::CaptureFormat( - KVMFRClipboardFormat format, DWORD sequence) + KVMFRClipboardFormat format, DWORD sequence, HWND owner, + DWORD& capturedSequence) { - if (GetClipboardSequenceNumber() != sequence) + capturedSequence = sequence; + if (GetClipboardSequenceNumber() != sequence || + GetClipboardOwner() != owner) { SetLastError(ERROR_RETRY); return nullptr; @@ -4488,7 +4559,8 @@ std::shared_ptr CClipboardManager::CaptureFormat( return nullptr; } - if (GetClipboardSequenceNumber() != sequence) + if (GetClipboardSequenceNumber() != sequence || + GetClipboardOwner() != owner) { CloseClipboard(); SetLastError(ERROR_RETRY); @@ -4635,7 +4707,12 @@ std::shared_ptr CClipboardManager::CaptureFormat( if (!success && !error) error = ERROR_NOT_ENOUGH_MEMORY; GlobalUnlock(handle); - if (GetClipboardSequenceNumber() != sequence) + const DWORD renderedSequence = GetClipboardSequenceNumber(); + const HWND renderedOwner = GetClipboardOwner(); + const bool sequenceChanged = renderedSequence != sequence; + // With the clipboard still open, another process cannot replace it. A + // sequence advance from the same owner is delayed or synthesized rendering. + if (renderedOwner != owner || (sequenceChanged && !owner)) { CloseClipboard(); SetLastError(ERROR_RETRY); @@ -4647,6 +4724,7 @@ std::shared_ptr CClipboardManager::CaptureFormat( SetLastError(error); return nullptr; } + capturedSequence = renderedSequence; return spool; } diff --git a/idd/LGIddHelper/CClipboardManager.h b/idd/LGIddHelper/CClipboardManager.h index 9dd1ac5e..83059464 100644 --- a/idd/LGIddHelper/CClipboardManager.h +++ b/idd/LGIddHelper/CClipboardManager.h @@ -222,14 +222,17 @@ private: std::atomic m_shutdown { false }; bool m_applyingRemote = false; bool m_available = false; - uint64_t m_epoch = 0; - uint64_t m_localGeneration = 0; - DWORD m_localSequence = 0; - DWORD m_localRetrySequence = 0; - uint64_t m_localRetryDeadline = 0; - uint64_t m_remoteGeneration = 0; - uint32_t m_remoteFormats = 0; - DWORD m_ownedSequence = 0; + uint64_t m_epoch = 0; + uint64_t m_localGeneration = 0; + DWORD m_localSequence = 0; + HWND m_localOwner = nullptr; + DWORD m_materializedSequence = 0; + uint64_t m_materializedGeneration = 0; + DWORD m_localRetrySequence = 0; + uint64_t m_localRetryDeadline = 0; + uint64_t m_remoteGeneration = 0; + uint32_t m_remoteFormats = 0; + DWORD m_ownedSequence = 0; std::atomic m_liveLocalGeneration { 0 }; std::atomic m_nextTransfer { KVMFR_CLIPBOARD_TRANSFER_HELPER | UINT64_C(1) }; @@ -321,7 +324,8 @@ private: KVMFRClipboardFormat ToWireFormat(UINT format) const; UINT ToWindowsFormat(KVMFRClipboardFormat format) const; std::shared_ptr CaptureFormat( - KVMFRClipboardFormat format, DWORD sequence); + KVMFRClipboardFormat format, DWORD sequence, HWND owner, + DWORD& capturedSequence); bool MaterializeFormat(KVMFRClipboardFormat format, UINT windowsFormat, CClipboardSpool& spool);