Files
LookingGlass/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h
Geoffrey McRae 9e9bf6b88c [idd] lgmp: use precise clipboard polling
Drive adaptive clipboard stream polling with a high-resolution waitable
timer instead of rounding microsecond delays into coarse wait-function
timeouts.

Preserve the explicit stop and local wake events while avoiding a clock-tick
stall at each file-transfer window.
2026-08-15 16:03:43 +10:00

246 lines
7.9 KiB
C++

/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "CSRWLock.h"
#include "transport/IClipboardSource.h"
#include "transport/lgmp/CLGMPClipboardFiles.h"
#include "common/KVMFRClipboard.h"
#include "common/LGMPConfig.h"
#include <Windows.h>
#include <stdint.h>
extern "C" {
#include "lgmp/host.h"
#include "lgmp/stream.h"
}
class CLGMPHost;
class CLGMPClipboardTransport final : public IClipboardSource
{
private:
static constexpr unsigned MEMORY_COUNT =
KVMFR_CLIPBOARD_STREAM_SLOT_COUNT;
static constexpr unsigned INTERNAL_TARGET_COUNT =
CLGMPClipboardFiles::MAX_ACQUISITIONS +
CLGMPClipboardFiles::MAX_REQUESTS + 3;
static constexpr ULONGLONG OWNER_LEASE_MS = 1000;
static constexpr ULONGLONG STREAM_DRAIN_TIMEOUT_MS = 1000;
static constexpr DWORD ACTIVE_POLL_MS = 5;
static constexpr DWORD IDLE_POLL_MS = 50;
static constexpr unsigned STREAM_TARGET_COUNT =
KVMFR_CLIPBOARD_STREAM_SLOT_COUNT;
static constexpr unsigned STREAM_DRAIN_MAX = 64;
static constexpr uint32_t SLOT_BYTES =
KVMFR_CLIPBOARD_STREAM_RECORD_BYTES;
enum class PostResult
{
POSTED,
BUSY,
GONE,
FAILED,
};
struct Transfer
{
uint64_t transfer = 0;
uint64_t clipboardGeneration = 0;
uint64_t nextOffset = 0;
uint64_t sizeHint = KVMFR_CLIPBOARD_SIZE_UNKNOWN;
KVMFRClipboardFormat format = KVMFR_CLIPBOARD_FORMAT_NONE;
uint32_t nextSequence = 0;
bool began = false;
void Clear() { *this = {}; }
bool Active() const { return transfer != 0; }
};
struct PendingTarget
{
bool valid = false;
bool acknowledge = false;
KVMFRClipboardMessage record = {};
uint8_t data[KVMFR_CLIPBOARD_DATA_BYTES] = {};
void Clear()
{
valid = false;
acknowledge = false;
record = {};
}
};
struct StreamTarget
{
KVMFRClipboardMessage record = {};
uint8_t data[KVMFR_CLIPBOARD_DATA_BYTES] = {};
void Clear() { record = {}; }
};
CLGMPHost& m_host;
PLGMPHostQueue m_queue = nullptr;
PLGMPHostStream m_hostToClientStream = nullptr;
PLGMPHostStream m_clientToHostStream = nullptr;
PLGMPMemory m_statusMemory [MEMORY_COUNT] = {};
PLGMPMemory m_messageMemory[MEMORY_COUNT] = {};
CSRWLock m_lifecycleLock;
CSRWLock m_lock;
IClipboardTarget * m_target = nullptr;
HANDLE m_stopEvent = nullptr;
HANDLE m_wakeEvent = nullptr;
HANDLE m_pollTimer = nullptr;
HANDLE m_thread = nullptr;
bool m_available = false;
bool m_statusDirty = true;
bool m_cachedValid = false;
bool m_replayPending = false;
bool m_outboundBlocked = false;
bool m_ownerReleasing = false;
bool m_releaseClearHelper = false;
bool m_failed = false;
uint32_t m_endpointGeneration = 0;
uint32_t m_ownerClientID = 0;
uint32_t m_ownerGeneration = 0;
uint32_t m_statusSerial = 0;
uint32_t m_messageSerial = 0;
uint32_t m_helperFormats = 0;
uint32_t m_clientFormats = 0;
uint32_t m_blockedOwnerClientID = 0;
uint32_t m_blockedOwnerGeneration = 0;
uint64_t m_ownerDeadline = 0;
uint64_t m_ownerDrainDeadline = 0;
uint64_t m_clientClipboardGeneration = 0;
uint64_t m_discardClientToHelper = 0;
uint64_t m_discardHelperToClient = 0;
const char * m_ownerReleaseReason = nullptr;
KVMFRClipboardMessage m_cachedClipboard = {};
KVMFRClipboardMessage m_blockedOutbound = {};
KVMFRStreamDescriptor m_hostToClientDescriptor = {};
KVMFRStreamDescriptor m_clientToHostDescriptor = {};
Transfer m_clientToHelper;
Transfer m_helperToClient;
PendingTarget m_pendingTarget;
StreamTarget m_streamTarget[STREAM_TARGET_COUNT];
unsigned m_streamTargetHead = 0;
unsigned m_streamTargetCount = 0;
KVMFRClipboardMessage m_internalTarget[INTERNAL_TARGET_COUNT] = {};
unsigned m_internalTargetCount = 0;
CLGMPClipboardFiles m_files;
bool Initialize();
void DeInit();
bool InitializeStreams();
void DeInitStreams();
bool BindStreams(uint32_t clientID) const;
bool TryUnbindStreams();
void ForceUnbindStreams() const;
static DWORD CALLBACK ThreadProc(void * context);
void Thread();
void Wake() const;
bool IsOwner(uint32_t clientID, uint32_t generation) const;
bool OwnerSubscribed() const;
void RenewLease();
void BlockOutbound(const KVMFRClipboardMessage& record,
uint32_t ownerClientID, uint32_t ownerGeneration);
void ClearOutboundBlock();
bool BlockedOwnerLost(const KVMFRClipboardMessage& record) const;
void ReleaseOwner(const char * reason, bool clearHelper,
bool force = false);
void FinishOwnerRelease();
bool RetryOwnerRelease();
void ResetProtocol(bool keepClipboard);
void QueueHelperClear();
void QueueTransferCancel(const Transfer& transfer);
void QueueStaleRequestCancel(
const KVMFRClipboardMessage& request);
void QueueStaleFileRecord(const KVMFRClipboardMessage& record);
void QueueFileDisconnect();
bool QueueInternalTarget(const KVMFRClipboardMessage& record);
bool PumpInternalTarget();
void ClearStreamTargets();
bool QueueStreamTarget(const KVMFRClipboardMessage& record,
const uint8_t * data);
bool PumpStreamTarget();
bool DrainStream(bool& received);
bool ProcessStreamTarget(const KVMFRClipboardMessage& record,
const uint8_t * data);
PLGMPMemory FindAvailable(PLGMPMemory (&memory)[MEMORY_COUNT]) const;
PostResult PostForOwner(uint64_t udata, PLGMPMemory memory);
bool PublishStatus();
bool ReplayClipboard();
bool DrainMessage();
bool ProcessMessage(uint32_t clientID,
const KVMFRClipboardMessage& message);
bool RetryTarget();
void FinishTarget(bool accepted);
void DropPendingTarget();
void Fail(const char * operation, LGMP_STATUS status);
bool ValidateClaim(const KVMFRClipboardMessage& message) const;
bool ValidateOwnedControl(const KVMFRClipboardMessage& message) const;
bool ValidateInboundRecord(const KVMFRClipboardMessage& message) const;
bool ValidateOutboundRecord(const KVMFRClipboardMessage& message) const;
bool ValidateChunk(const Transfer& transfer,
const KVMFRClipboardMessage& message) const;
void AdvanceChunk(Transfer& transfer,
const KVMFRClipboardMessage& message);
void ApplyInbound(const KVMFRClipboardMessage& message);
void ApplyOutbound(const KVMFRClipboardMessage& message);
bool BeginTarget(const KVMFRClipboardMessage& message,
const uint8_t * data, bool acknowledge);
ClipboardChannelResult SendControl(
const KVMFRClipboardMessage& record);
ClipboardChannelResult SendData(
const KVMFRClipboardMessage& record, const uint8_t * data);
friend class CLGMPTransport;
public:
explicit CLGMPClipboardTransport(CLGMPHost& host) :
m_host(host) {}
~CLGMPClipboardTransport() override;
CLGMPClipboardTransport(const CLGMPClipboardTransport&) = delete;
CLGMPClipboardTransport& operator=(
const CLGMPClipboardTransport&) = delete;
bool Start(IClipboardTarget& target) override;
void Stop() override;
void ClipboardState(bool available, uint32_t generation) override;
ClipboardChannelResult SendClipboard(
const KVMFRClipboardMessage& record,
const uint8_t * data) override;
void ClipboardReset(uint32_t generation, uint32_t reason) override;
};