Files
LookingGlass/idd/LGCommon/CClipboardChannel.h
Geoffrey McRae 89b1ced8fc [idd] clipboard: pipeline helper channel
Publish ordered four-record prefixes through the Helper mapping and ring
once per accepted burst. Wake blocked producers when the peer returns
credits while retaining the bounded polling fallback.

Move received payload ownership into manager work items and place remote
file-read data directly into the waiting caller’s bounded output buffer.
This removes avoidable 256 KiB copies without changing record ordering,
timeouts, cancellation, or reset semantics.
2026-08-15 14:51:16 +10:00

149 lines
4.3 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 "Atomic.h"
#include "ClipboardRing.h"
#include "CSRWLock.h"
#include <Windows.h>
#include <atomic>
#include <stddef.h>
#include <stdint.h>
#include <vector>
enum class ClipboardChannelResult
{
ACCEPTED,
BUSY,
FAILED,
};
struct ClipboardChannelWrite
{
KVMFRClipboardMessage record = {};
const void * data = nullptr;
};
class IClipboardChannelHandler
{
public:
virtual ~IClipboardChannelHandler() = default;
virtual void ClipboardState(bool available, uint64_t epoch) = 0;
// data owns record.length bytes and may be moved by the handler. BUSY
// leaves the shared ring slot occupied so it can be reconstructed and
// retried later.
virtual ClipboardChannelResult ClipboardRecord(
const KVMFRClipboardMessage& record,
std::vector<uint8_t>&& data) = 0;
virtual void ClipboardReset(uint64_t epoch, uint32_t reason) = 0;
};
class IClipboardChannelDoorbell
{
public:
virtual ~IClipboardChannelDoorbell() = default;
virtual bool ClipboardKick(uint64_t epoch) = 0;
virtual void ClipboardResetPeer(uint64_t epoch, uint32_t reason) = 0;
};
class CClipboardChannel
{
private:
static constexpr DWORD POLL_MS = 50;
enum class DrainResult
{
IDLE,
BUSY,
STOPPED,
CORRUPT,
};
CSRWLock m_lifecycleLock;
CSRWLock m_handlerLock;
CSRWLock m_writeLock;
HANDLE m_mapping = nullptr;
ClipboardMapping * m_view = nullptr;
ClipboardRing * m_in = nullptr;
ClipboardRing * m_out = nullptr;
HANDLE m_stop = nullptr;
HANDLE m_kick = nullptr;
HANDLE m_writeReady = nullptr;
HANDLE m_thread = nullptr;
DWORD m_threadId = 0;
uint64_t m_epoch = 0;
uint64_t m_instance = 0;
bool m_deferredDetach = false;
IClipboardChannelHandler * m_handler = nullptr;
IClipboardChannelDoorbell * m_doorbell = nullptr;
std::atomic<bool> m_available { false };
DrainResult Drain();
void Thread();
void Fail(uint64_t instance, uint32_t reason, bool resetPeer = true);
void CleanupDeferredDetach();
void PublishState(bool available, uint64_t epoch);
ClipboardChannelResult PublishRecord(
const KVMFRClipboardMessage& record,
std::vector<uint8_t>&& data);
void PublishReset(uint64_t epoch, uint32_t reason);
static DWORD WINAPI ThreadProc(void * context);
public:
CClipboardChannel() = default;
~CClipboardChannel();
CClipboardChannel(const CClipboardChannel&) = delete;
CClipboardChannel& operator=(const CClipboardChannel&) = delete;
bool Attach(HANDLE mapping, uint64_t epoch, bool helper,
IClipboardChannelDoorbell& doorbell);
void Detach();
void Kick(uint64_t epoch);
void Reset(uint64_t epoch, uint32_t reason);
bool WaitWritable(HANDLE stop, DWORD timeout);
ClipboardChannelResult Send(
const KVMFRClipboardMessage& record, const void * data = nullptr);
// Publish an ordered prefix of records, bounded by the physical ring
// window. BUSY may return a non-zero accepted count; the caller must resume
// at that prefix boundary. A single doorbell covers the entire prefix.
ClipboardChannelResult SendBatch(const ClipboardChannelWrite * records,
size_t count, size_t& accepted);
void SetHandler(IClipboardChannelHandler * handler);
void ClearHandler(IClipboardChannelHandler * handler);
bool Available() const
{
return Atomic::Load(m_available, std::memory_order_acquire);
}
uint64_t Epoch();
};