[idd] clipboard: add direct synchronization

This commit is contained in:
Geoffrey McRae
2026-08-14 06:26:35 +10:00
parent a8d531b797
commit b786167f72
35 changed files with 5956 additions and 79 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
/**
* 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 "common/KVMFRClipboard.h"
#include "common/LGMPConfig.h"
#include <Windows.h>
#include <stdint.h>
extern "C" {
#include "lgmp/host.h"
}
class CLGMPHost;
class CLGMPClipboardTransport final : public IClipboardSource
{
private:
static constexpr unsigned MEMORY_COUNT = 2;
static constexpr unsigned INTERNAL_TARGET_COUNT = 3;
static constexpr ULONGLONG OWNER_LEASE_MS = 1000;
static constexpr DWORD ACTIVE_POLL_MS = 5;
static constexpr DWORD IDLE_POLL_MS = 50;
static constexpr uint32_t SLOT_BYTES =
sizeof(KVMFRClipboardSlotHeader) + KVMFR_CLIPBOARD_DATA_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 Grant
{
uint32_t generation = 0;
bool offered = false;
bool committed = false;
};
struct PendingTarget
{
bool valid = false;
bool acknowledge = false;
int grant = -1;
KVMFRClipboardMessage record = {};
uint8_t data[KVMFR_CLIPBOARD_DATA_BYTES] = {};
void Clear()
{
valid = false;
acknowledge = false;
grant = -1;
record = {};
}
};
CLGMPHost& m_host;
PLGMPHostQueue m_queue = nullptr;
PLGMPMemory m_statusMemory [MEMORY_COUNT] = {};
PLGMPMemory m_messageMemory[MEMORY_COUNT] = {};
PLGMPMemory m_grantMemory [MEMORY_COUNT] = {};
PLGMPMemory m_dataMemory [MEMORY_COUNT] = {};
CSRWLock m_lifecycleLock;
CSRWLock m_lock;
IClipboardTarget * m_target = nullptr;
HANDLE m_stopEvent = nullptr;
HANDLE m_wakeEvent = 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_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_dataSerial = 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_clientClipboardGeneration = 0;
uint64_t m_discardHelperToClient = 0;
KVMFRClipboardMessage m_cachedClipboard = {};
KVMFRClipboardMessage m_blockedOutbound = {};
Transfer m_clientToHelper;
Transfer m_helperToClient;
Grant m_grants[MEMORY_COUNT];
PendingTarget m_pendingTarget;
KVMFRClipboardMessage m_internalTarget[INTERNAL_TARGET_COUNT] = {};
unsigned m_internalTargetCount = 0;
bool Initialize();
void DeInit();
static DWORD CALLBACK ThreadProc(void * context);
void Thread();
void Wake();
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);
void ResetProtocol(bool keepClipboard);
void QueueHelperClear();
void QueueTransferCancel(const Transfer& transfer);
void QueueStaleRequestCancel(
const KVMFRClipboardMessage& request);
bool QueueInternalTarget(const KVMFRClipboardMessage& record);
bool PumpInternalTarget();
PLGMPMemory FindAvailable(PLGMPMemory (&memory)[MEMORY_COUNT]) const;
PostResult PostForOwner(uint64_t udata, PLGMPMemory memory);
bool PublishStatus();
bool PostGrants();
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, int grant);
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;
};

View File

@@ -52,7 +52,8 @@ bool CLGMPHost::Initialize(CIVSHMEM& ivshmem)
KVMFR_FEATURE_SETCURSORPOS |
KVMFR_FEATURE_WINDOWSIZE |
KVMFR_FEATURE_FRAME_SCHEDULE |
KVMFR_FEATURE_INPUT;
KVMFR_FEATURE_INPUT |
KVMFR_FEATURE_CLIPBOARD;
strncpy_s(kvmfr.hostver, LG_VERSION_STR, sizeof(kvmfr.hostver) - 1);
ss.write(reinterpret_cast<const char *>(&kvmfr), sizeof(kvmfr));
}

View File

@@ -67,7 +67,8 @@ CLGMPTransport::CLGMPTransport(const TransportInstance& config) :
m_config(config),
m_control(m_host),
m_frames(m_host, m_ivshmem),
m_input(m_host)
m_input(m_host),
m_clipboard(m_host)
{
}
@@ -90,10 +91,10 @@ bool CLGMPTransport::Initialize()
if (!m_host.Initialize(m_ivshmem))
return false;
// Preserve the existing shared-memory layout by appending input after the
// frame and pointer queues and retained pointer state allocations.
// Preserve the existing shared-memory layout by appending input and then
// clipboard after the frame/pointer queues and retained pointer state.
if (!m_frames.Initialize() || !m_control.Initialize() ||
!m_input.Initialize())
!m_input.Initialize() || !m_clipboard.Initialize())
return false;
m_frames.SealMemoryLayout();
@@ -242,6 +243,7 @@ void CLGMPTransport::Stop()
Atomic::Store(m_ready, false, std::memory_order_release);
Abort();
m_hasActive = false;
m_clipboard.Stop();
m_input.Stop();
}

View File

@@ -23,6 +23,7 @@
#include "Atomic.h"
#include "transport/ITransport.h"
#include "transport/lgmp/CIVSHMEM.h"
#include "transport/lgmp/CLGMPClipboardTransport.h"
#include "transport/lgmp/CLGMPControl.h"
#include "transport/lgmp/CLGMPFrameTransport.h"
#include "transport/lgmp/CLGMPHost.h"
@@ -32,20 +33,21 @@
class CLGMPTransport final : public ITransport
{
private:
TransportInstance m_config;
TransportInstance m_config;
// Keep this declaration order. Destruction must release frame and control
// allocations before the LGMP host and its IVSHMEM mapping are destroyed.
CIVSHMEM m_ivshmem;
CLGMPHost m_host;
CLGMPControl m_control;
CLGMPFrameTransport m_frames;
CLGMPInputTransport m_input;
CRecovery m_recovery;
std::atomic<bool> m_ready = false;
FrameCfg m_activeCfg;
FrameCfg m_pendingCfg;
bool m_hasActive = false;
bool m_hasPending = false;
CIVSHMEM m_ivshmem;
CLGMPHost m_host;
CLGMPControl m_control;
CLGMPFrameTransport m_frames;
CLGMPInputTransport m_input;
CLGMPClipboardTransport m_clipboard;
CRecovery m_recovery;
std::atomic<bool> m_ready = false;
FrameCfg m_activeCfg;
FrameCfg m_pendingCfg;
bool m_hasActive = false;
bool m_hasPending = false;
public:
explicit CLGMPTransport(const TransportInstance& config);
@@ -76,4 +78,5 @@ public:
IFrameSink * FrameSink() override { return &m_frames; }
IControlSink * Control() override { return &m_control; }
IInputSource * Input() override { return &m_input; }
IClipboardSource * Clipboard() override { return &m_clipboard; }
};