mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
[idd] clipboard: add direct synchronization
This commit is contained in:
578
idd/LGCommon/CClipboardChannel.cpp
Normal file
578
idd/LGCommon/CClipboardChannel.cpp
Normal file
@@ -0,0 +1,578 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "CClipboardChannel.h"
|
||||
|
||||
#include "CClipboardRing.h"
|
||||
#include "CDebug.h"
|
||||
|
||||
#include <new>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
static constexpr DWORD WAIT_FIRST_OBJECT_VALUE = 0;
|
||||
|
||||
struct ClipboardCallbackScope
|
||||
{
|
||||
CClipboardChannel * channel;
|
||||
ClipboardCallbackScope * previous;
|
||||
};
|
||||
|
||||
thread_local ClipboardCallbackScope * g_callbackScope = nullptr;
|
||||
|
||||
bool InClipboardCallback(CClipboardChannel * channel)
|
||||
{
|
||||
for (ClipboardCallbackScope * scope = g_callbackScope;
|
||||
scope; scope = scope->previous)
|
||||
if (scope->channel == channel)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
class CClipboardCallbackScope
|
||||
{
|
||||
private:
|
||||
ClipboardCallbackScope m_scope;
|
||||
|
||||
public:
|
||||
explicit CClipboardCallbackScope(CClipboardChannel * channel) :
|
||||
m_scope { channel, g_callbackScope }
|
||||
{
|
||||
g_callbackScope = &m_scope;
|
||||
}
|
||||
|
||||
~CClipboardCallbackScope()
|
||||
{
|
||||
g_callbackScope = m_scope.previous;
|
||||
}
|
||||
};
|
||||
|
||||
bool ValidRecord(const KVMFRClipboardMessage& record,
|
||||
bool dataPresent)
|
||||
{
|
||||
if (record.version != KVMFR_CLIPBOARD_VERSION ||
|
||||
record.length > KVMFR_CLIPBOARD_DATA_BYTES ||
|
||||
(record.length != 0) != dataPresent)
|
||||
return false;
|
||||
|
||||
switch (record.type)
|
||||
{
|
||||
case KVMFR_CLIPBOARD_MESSAGE_OFFER:
|
||||
return record.clipboardGeneration && record.token &&
|
||||
!(record.token & ~KVMFR_CLIPBOARD_FORMAT_MASK_ALL) &&
|
||||
!record.transfer && !record.offset && !record.size &&
|
||||
!record.format && !record.flags && !record.length &&
|
||||
!record.sequence;
|
||||
|
||||
case KVMFR_CLIPBOARD_MESSAGE_CLEAR:
|
||||
return record.clipboardGeneration && !record.token &&
|
||||
!record.transfer && !record.offset && !record.size &&
|
||||
!record.format && !record.flags && !record.length &&
|
||||
!record.sequence;
|
||||
|
||||
case KVMFR_CLIPBOARD_MESSAGE_REQUEST:
|
||||
return record.clipboardGeneration && record.transfer &&
|
||||
kvmfrClipboardFormatValid(record.format) && !record.offset &&
|
||||
!record.size && !record.flags && !record.token &&
|
||||
!record.length && !record.sequence;
|
||||
|
||||
case KVMFR_CLIPBOARD_MESSAGE_DATA:
|
||||
{
|
||||
if (!record.clipboardGeneration || !record.transfer ||
|
||||
!kvmfrClipboardFormatValid(record.format) || record.token ||
|
||||
(record.flags & ~(KVMFR_CLIPBOARD_FLAG_BEGIN |
|
||||
KVMFR_CLIPBOARD_FLAG_END)) ||
|
||||
(!record.length && !(record.flags & KVMFR_CLIPBOARD_FLAG_END)) ||
|
||||
record.offset > UINT64_MAX - record.length)
|
||||
return false;
|
||||
const uint64_t end = record.offset + record.length;
|
||||
if (record.flags & KVMFR_CLIPBOARD_FLAG_END)
|
||||
return record.size == end;
|
||||
if (!(record.flags & KVMFR_CLIPBOARD_FLAG_BEGIN))
|
||||
return record.size == KVMFR_CLIPBOARD_SIZE_UNKNOWN;
|
||||
return record.size == KVMFR_CLIPBOARD_SIZE_UNKNOWN ||
|
||||
record.size >= end;
|
||||
}
|
||||
|
||||
case KVMFR_CLIPBOARD_MESSAGE_CANCEL:
|
||||
return record.transfer && !record.offset && !record.size &&
|
||||
(!record.format || kvmfrClipboardFormatValid(record.format)) &&
|
||||
!record.flags && !record.length && !record.sequence;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CClipboardChannel::~CClipboardChannel()
|
||||
{
|
||||
Detach();
|
||||
}
|
||||
|
||||
bool CClipboardChannel::Attach(HANDLE mapping, uint64_t epoch,
|
||||
bool helper, IClipboardChannelDoorbell& doorbell)
|
||||
{
|
||||
if (!mapping || mapping == INVALID_HANDLE_VALUE || !epoch)
|
||||
{
|
||||
if (mapping && mapping != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(mapping);
|
||||
return false;
|
||||
}
|
||||
|
||||
Detach();
|
||||
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
ClipboardMapping * view = static_cast<ClipboardMapping *>(MapViewOfFile(
|
||||
mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(ClipboardMapping)));
|
||||
if (!view)
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(), "Failed to map clipboard channel");
|
||||
CloseHandle(mapping);
|
||||
return false;
|
||||
}
|
||||
if (!CClipboardRing::Valid(*view, epoch))
|
||||
{
|
||||
DEBUG_ERROR("Invalid clipboard mapping");
|
||||
UnmapViewOfFile(view);
|
||||
CloseHandle(mapping);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stop = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
m_kick = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if (!m_stop || !m_kick)
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(),
|
||||
"Failed to create clipboard channel events");
|
||||
if (m_kick)
|
||||
CloseHandle(m_kick);
|
||||
if (m_stop)
|
||||
CloseHandle(m_stop);
|
||||
m_kick = nullptr;
|
||||
m_stop = nullptr;
|
||||
UnmapViewOfFile(view);
|
||||
CloseHandle(mapping);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_mapping = mapping;
|
||||
m_view = view;
|
||||
m_in = helper ? &view->iddToHelper : &view->helperToIdd;
|
||||
m_out = helper ? &view->helperToIdd : &view->iddToHelper;
|
||||
m_epoch = epoch;
|
||||
++m_instance;
|
||||
if (!m_instance)
|
||||
++m_instance;
|
||||
m_doorbell = &doorbell;
|
||||
Atomic::Store(m_available, true, std::memory_order_release);
|
||||
|
||||
m_thread = CreateThread(
|
||||
nullptr, 0, ThreadProc, this, 0, &m_threadId);
|
||||
if (!m_thread)
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(),
|
||||
"Failed to create clipboard channel worker");
|
||||
Atomic::Store(m_available, false, std::memory_order_release);
|
||||
m_doorbell = nullptr;
|
||||
m_epoch = 0;
|
||||
m_out = nullptr;
|
||||
m_in = nullptr;
|
||||
m_threadId = 0;
|
||||
m_view = nullptr;
|
||||
m_mapping = nullptr;
|
||||
CloseHandle(m_kick);
|
||||
CloseHandle(m_stop);
|
||||
UnmapViewOfFile(view);
|
||||
CloseHandle(mapping);
|
||||
m_kick = nullptr;
|
||||
m_stop = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
lock.Unlock();
|
||||
PublishState(true, epoch);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CClipboardChannel::Detach()
|
||||
{
|
||||
HANDLE thread;
|
||||
uint64_t epoch;
|
||||
bool available;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
available = Atomic::Swap(
|
||||
m_available, false, std::memory_order_acq_rel);
|
||||
epoch = m_epoch;
|
||||
thread = m_thread;
|
||||
if (m_stop)
|
||||
SetEvent(m_stop);
|
||||
if (thread && InClipboardCallback(this))
|
||||
m_deferredDetach = true;
|
||||
}
|
||||
|
||||
// A callback may run on the worker or while the worker waits for the
|
||||
// callback-quiescence lock. Let the worker release channel resources after
|
||||
// this callback returns instead of waiting on it here.
|
||||
if (thread && InClipboardCallback(this))
|
||||
{
|
||||
if (available)
|
||||
PublishState(false, epoch);
|
||||
return;
|
||||
}
|
||||
|
||||
if (thread)
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
if (m_thread)
|
||||
CloseHandle(m_thread);
|
||||
if (m_kick)
|
||||
CloseHandle(m_kick);
|
||||
if (m_stop)
|
||||
CloseHandle(m_stop);
|
||||
if (m_view)
|
||||
UnmapViewOfFile(m_view);
|
||||
if (m_mapping)
|
||||
CloseHandle(m_mapping);
|
||||
|
||||
m_thread = nullptr;
|
||||
m_threadId = 0;
|
||||
m_kick = nullptr;
|
||||
m_stop = nullptr;
|
||||
m_in = nullptr;
|
||||
m_out = nullptr;
|
||||
m_view = nullptr;
|
||||
m_mapping = nullptr;
|
||||
m_doorbell = nullptr;
|
||||
m_epoch = 0;
|
||||
m_deferredDetach = false;
|
||||
lock.Unlock();
|
||||
if (available)
|
||||
PublishState(false, epoch);
|
||||
}
|
||||
|
||||
void CClipboardChannel::Kick(uint64_t epoch)
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
if (Available() && epoch == m_epoch && m_kick)
|
||||
SetEvent(m_kick);
|
||||
}
|
||||
|
||||
void CClipboardChannel::Reset(uint64_t epoch, uint32_t reason)
|
||||
{
|
||||
uint64_t instance;
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
if (!Available() || epoch != m_epoch)
|
||||
return;
|
||||
instance = m_instance;
|
||||
}
|
||||
// A peer reset is terminal for this mapping too. Keep the control pipe
|
||||
// connected, but stop both clipboard workers so neither endpoint can
|
||||
// continue publishing into a ring the other side has abandoned.
|
||||
Fail(instance, reason, false);
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardChannel::Send(
|
||||
const KVMFRClipboardMessage& record, const void * data)
|
||||
{
|
||||
if (!ValidRecord(record, data != nullptr))
|
||||
return ClipboardChannelResult::FAILED;
|
||||
|
||||
CSRWSharedLock lifecycleLock(m_lifecycleLock);
|
||||
if (!Available() || !m_out || !m_doorbell)
|
||||
return ClipboardChannelResult::FAILED;
|
||||
|
||||
{
|
||||
CSRWExclusiveLock writeLock(m_writeLock);
|
||||
uint32_t ticket;
|
||||
ClipboardRingSlot * slot = CClipboardRing::BeginWrite(*m_out, ticket);
|
||||
if (!slot)
|
||||
return ClipboardChannelResult::BUSY;
|
||||
|
||||
slot->header = record;
|
||||
if (record.length)
|
||||
memcpy(slot->data, data, record.length);
|
||||
if (!CClipboardRing::EndWrite(*m_out, ticket))
|
||||
return ClipboardChannelResult::FAILED;
|
||||
}
|
||||
|
||||
// Once the producer index advances the record belongs to the channel.
|
||||
// Doorbells are only a latency optimization; the peer also polls.
|
||||
m_doorbell->ClipboardKick(m_epoch);
|
||||
return ClipboardChannelResult::ACCEPTED;
|
||||
}
|
||||
|
||||
void CClipboardChannel::SetHandler(IClipboardChannelHandler * handler)
|
||||
{
|
||||
if (InClipboardCallback(this))
|
||||
{
|
||||
m_handler = handler;
|
||||
}
|
||||
else
|
||||
{
|
||||
CSRWExclusiveLock lock(m_handlerLock);
|
||||
m_handler = handler;
|
||||
}
|
||||
|
||||
if (!handler)
|
||||
return;
|
||||
|
||||
uint64_t epoch;
|
||||
bool available;
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
available = Available();
|
||||
epoch = m_epoch;
|
||||
}
|
||||
PublishState(available, epoch);
|
||||
if (available)
|
||||
Kick(epoch);
|
||||
}
|
||||
|
||||
void CClipboardChannel::ClearHandler(IClipboardChannelHandler * handler)
|
||||
{
|
||||
if (InClipboardCallback(this))
|
||||
{
|
||||
if (m_handler == handler)
|
||||
m_handler = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// The exclusive callback lock makes return from ClearHandler a
|
||||
// synchronous callback-quiescence point.
|
||||
CSRWExclusiveLock lock(m_handlerLock);
|
||||
if (m_handler == handler)
|
||||
m_handler = nullptr;
|
||||
}
|
||||
|
||||
CClipboardChannel::DrainResult CClipboardChannel::Drain()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
KVMFRClipboardMessage record = {};
|
||||
std::vector<uint8_t> data;
|
||||
uint32_t ticket;
|
||||
{
|
||||
CSRWSharedLock lifecycleLock(m_lifecycleLock);
|
||||
if (!Available() || !m_in || !m_doorbell)
|
||||
return DrainResult::STOPPED;
|
||||
|
||||
const ClipboardRingSlot * slot = nullptr;
|
||||
const ClipboardRingReadResult read =
|
||||
CClipboardRing::BeginRead(*m_in, ticket, slot);
|
||||
if (read == ClipboardRingReadResult::EMPTY)
|
||||
return DrainResult::IDLE;
|
||||
if (read == ClipboardRingReadResult::CORRUPT)
|
||||
return DrainResult::CORRUPT;
|
||||
|
||||
record = slot->header;
|
||||
if (CClipboardRing::Valid(*slot) &&
|
||||
ValidRecord(record, record.length != 0) && record.length)
|
||||
{
|
||||
try
|
||||
{
|
||||
data.resize(record.length);
|
||||
memcpy(data.data(), slot->data, record.length);
|
||||
}
|
||||
catch (const std::bad_alloc&)
|
||||
{
|
||||
return DrainResult::BUSY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardChannelResult result =
|
||||
ValidRecord(record, record.length != 0) ?
|
||||
PublishRecord(record, data.empty() ? nullptr : data.data()) :
|
||||
ClipboardChannelResult::FAILED;
|
||||
if (result == ClipboardChannelResult::BUSY)
|
||||
return DrainResult::BUSY;
|
||||
|
||||
IClipboardChannelDoorbell * doorbell;
|
||||
uint64_t epoch;
|
||||
{
|
||||
CSRWSharedLock lifecycleLock(m_lifecycleLock);
|
||||
if (!Available() || !m_in || !m_doorbell)
|
||||
return DrainResult::STOPPED;
|
||||
if (!CClipboardRing::EndRead(*m_in, ticket))
|
||||
return DrainResult::CORRUPT;
|
||||
doorbell = m_doorbell;
|
||||
epoch = m_epoch;
|
||||
}
|
||||
|
||||
if (result == ClipboardChannelResult::FAILED)
|
||||
{
|
||||
// The invalid record has been consumed so it cannot be retried. Make
|
||||
// this endpoint terminal as well; Thread::Fail performs the single
|
||||
// local callback and peer reset outside the ring/lifecycle locks.
|
||||
return DrainResult::CORRUPT;
|
||||
}
|
||||
else
|
||||
doorbell->ClipboardKick(epoch);
|
||||
}
|
||||
}
|
||||
|
||||
void CClipboardChannel::Thread()
|
||||
{
|
||||
HANDLE events[] = { m_stop, m_kick };
|
||||
const uint64_t instance = m_instance;
|
||||
for (;;)
|
||||
{
|
||||
const DWORD result = WaitForMultipleObjects(
|
||||
ARRAYSIZE(events), events, FALSE, POLL_MS);
|
||||
if (result == WAIT_FIRST_OBJECT_VALUE)
|
||||
break;
|
||||
if (result != WAIT_FIRST_OBJECT_VALUE + 1 && result != WAIT_TIMEOUT)
|
||||
{
|
||||
const DWORD error = GetLastError();
|
||||
DEBUG_ERROR_HR(error,
|
||||
"Failed to wait for clipboard channel work");
|
||||
Fail(instance, error ? error : ERROR_GEN_FAILURE);
|
||||
break;
|
||||
}
|
||||
|
||||
const DrainResult drain = Drain();
|
||||
if (drain == DrainResult::STOPPED)
|
||||
break;
|
||||
if (drain == DrainResult::CORRUPT)
|
||||
{
|
||||
Fail(instance, ERROR_INVALID_DATA);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CleanupDeferredDetach();
|
||||
}
|
||||
|
||||
void CClipboardChannel::Fail(
|
||||
uint64_t instance, uint32_t reason, bool resetPeer)
|
||||
{
|
||||
IClipboardChannelDoorbell * doorbell;
|
||||
uint64_t epoch;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
if (instance != m_instance)
|
||||
return;
|
||||
if (!Atomic::Swap(m_available, false, std::memory_order_acq_rel))
|
||||
return;
|
||||
epoch = m_epoch;
|
||||
doorbell = m_doorbell;
|
||||
if (m_stop)
|
||||
SetEvent(m_stop);
|
||||
}
|
||||
|
||||
PublishReset(epoch, reason);
|
||||
if (resetPeer && doorbell)
|
||||
doorbell->ClipboardResetPeer(epoch, reason);
|
||||
PublishState(false, epoch);
|
||||
}
|
||||
|
||||
void CClipboardChannel::CleanupDeferredDetach()
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
if (!m_deferredDetach || m_threadId != GetCurrentThreadId())
|
||||
return;
|
||||
|
||||
if (m_kick)
|
||||
CloseHandle(m_kick);
|
||||
if (m_stop)
|
||||
CloseHandle(m_stop);
|
||||
if (m_view)
|
||||
UnmapViewOfFile(m_view);
|
||||
if (m_mapping)
|
||||
CloseHandle(m_mapping);
|
||||
|
||||
// A later external Detach closes the now-signaled thread handle.
|
||||
m_threadId = 0;
|
||||
m_kick = nullptr;
|
||||
m_stop = nullptr;
|
||||
m_in = nullptr;
|
||||
m_out = nullptr;
|
||||
m_view = nullptr;
|
||||
m_mapping = nullptr;
|
||||
m_doorbell = nullptr;
|
||||
m_epoch = 0;
|
||||
m_deferredDetach = false;
|
||||
}
|
||||
|
||||
void CClipboardChannel::PublishState(bool available, uint64_t epoch)
|
||||
{
|
||||
if (InClipboardCallback(this))
|
||||
{
|
||||
if (m_handler)
|
||||
m_handler->ClipboardState(available, epoch);
|
||||
return;
|
||||
}
|
||||
|
||||
CSRWExclusiveLock lock(m_handlerLock);
|
||||
if (m_handler)
|
||||
{
|
||||
CClipboardCallbackScope scope(this);
|
||||
m_handler->ClipboardState(available, epoch);
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardChannel::PublishRecord(
|
||||
const KVMFRClipboardMessage& record, const uint8_t * data)
|
||||
{
|
||||
if (InClipboardCallback(this))
|
||||
return m_handler ? m_handler->ClipboardRecord(record, data) :
|
||||
ClipboardChannelResult::BUSY;
|
||||
|
||||
CSRWExclusiveLock lock(m_handlerLock);
|
||||
if (!m_handler)
|
||||
return ClipboardChannelResult::BUSY;
|
||||
|
||||
CClipboardCallbackScope scope(this);
|
||||
return m_handler->ClipboardRecord(record, data);
|
||||
}
|
||||
|
||||
void CClipboardChannel::PublishReset(uint64_t epoch, uint32_t reason)
|
||||
{
|
||||
if (InClipboardCallback(this))
|
||||
{
|
||||
if (m_handler)
|
||||
m_handler->ClipboardReset(epoch, reason);
|
||||
return;
|
||||
}
|
||||
|
||||
CSRWExclusiveLock lock(m_handlerLock);
|
||||
if (m_handler)
|
||||
{
|
||||
CClipboardCallbackScope scope(this);
|
||||
m_handler->ClipboardReset(epoch, reason);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t CClipboardChannel::Epoch()
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
return m_epoch;
|
||||
}
|
||||
|
||||
DWORD WINAPI CClipboardChannel::ThreadProc(void * context)
|
||||
{
|
||||
static_cast<CClipboardChannel *>(context)->Thread();
|
||||
return 0;
|
||||
}
|
||||
129
idd/LGCommon/CClipboardChannel.h
Normal file
129
idd/LGCommon/CClipboardChannel.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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 <stdint.h>
|
||||
|
||||
enum class ClipboardChannelResult
|
||||
{
|
||||
ACCEPTED,
|
||||
BUSY,
|
||||
FAILED,
|
||||
};
|
||||
|
||||
class IClipboardChannelHandler
|
||||
{
|
||||
public:
|
||||
virtual ~IClipboardChannelHandler() = default;
|
||||
|
||||
virtual void ClipboardState(bool available, uint64_t epoch) = 0;
|
||||
// data is borrowed and remains valid only for the duration of this call.
|
||||
// BUSY leaves the shared ring slot occupied so it can be retried later.
|
||||
virtual ClipboardChannelResult ClipboardRecord(
|
||||
const KVMFRClipboardMessage& record, const 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_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, const 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);
|
||||
|
||||
ClipboardChannelResult Send(
|
||||
const KVMFRClipboardMessage& record, const void * data = nullptr);
|
||||
|
||||
void SetHandler(IClipboardChannelHandler * handler);
|
||||
void ClearHandler(IClipboardChannelHandler * handler);
|
||||
|
||||
bool Available() const
|
||||
{
|
||||
return Atomic::Load(m_available, std::memory_order_acquire);
|
||||
}
|
||||
uint64_t Epoch();
|
||||
};
|
||||
@@ -82,17 +82,21 @@ bool CClipboardRing::EndWrite(ClipboardRing& ring, uint32_t ticket)
|
||||
return true;
|
||||
}
|
||||
|
||||
const ClipboardRingSlot * CClipboardRing::BeginRead(
|
||||
ClipboardRing& ring, uint32_t& ticket)
|
||||
ClipboardRingReadResult CClipboardRing::BeginRead(ClipboardRing& ring,
|
||||
uint32_t& ticket, const ClipboardRingSlot *& slot)
|
||||
{
|
||||
slot = nullptr;
|
||||
const uint32_t consumed = Atomic::Load(ring.consumed);
|
||||
const uint32_t pending = Atomic::Load(ring.produced) - consumed;
|
||||
if (!pending || pending > KVMFR_CLIPBOARD_SLOT_COUNT)
|
||||
return nullptr;
|
||||
if (!pending)
|
||||
return ClipboardRingReadResult::EMPTY;
|
||||
if (pending > KVMFR_CLIPBOARD_SLOT_COUNT)
|
||||
return ClipboardRingReadResult::CORRUPT;
|
||||
|
||||
Atomic::Fence();
|
||||
ticket = consumed;
|
||||
return &ring.slots[consumed % KVMFR_CLIPBOARD_SLOT_COUNT];
|
||||
slot = &ring.slots[consumed % KVMFR_CLIPBOARD_SLOT_COUNT];
|
||||
return ClipboardRingReadResult::READY;
|
||||
}
|
||||
|
||||
bool CClipboardRing::EndRead(ClipboardRing& ring, uint32_t ticket)
|
||||
|
||||
@@ -22,6 +22,13 @@
|
||||
|
||||
#include "ClipboardRing.h"
|
||||
|
||||
enum class ClipboardRingReadResult
|
||||
{
|
||||
EMPTY,
|
||||
READY,
|
||||
CORRUPT,
|
||||
};
|
||||
|
||||
class CClipboardRing
|
||||
{
|
||||
public:
|
||||
@@ -32,8 +39,8 @@ public:
|
||||
ClipboardRing& ring, uint32_t& ticket);
|
||||
static bool EndWrite(ClipboardRing& ring, uint32_t ticket);
|
||||
|
||||
static const ClipboardRingSlot * BeginRead(
|
||||
ClipboardRing& ring, uint32_t& ticket);
|
||||
static ClipboardRingReadResult BeginRead(ClipboardRing& ring,
|
||||
uint32_t& ticket, const ClipboardRingSlot *& slot);
|
||||
static bool EndRead(ClipboardRing& ring, uint32_t ticket);
|
||||
|
||||
static bool Valid(const ClipboardRingSlot& slot);
|
||||
|
||||
@@ -310,6 +310,12 @@ bool CPipeEndpoint::Send(const void * message, size_t size)
|
||||
return success;
|
||||
}
|
||||
|
||||
HANDLE CPipeEndpoint::NativeHandle()
|
||||
{
|
||||
CSRWSharedLock lock(m_pipeLock);
|
||||
return m_pipe;
|
||||
}
|
||||
|
||||
DWORD WINAPI CPipeEndpoint::ThreadProc(void * context)
|
||||
{
|
||||
static_cast<CPipeEndpoint *>(context)->Thread();
|
||||
|
||||
@@ -69,6 +69,8 @@ public:
|
||||
bool IsRunning() const { return Atomic::Load(m_running); }
|
||||
bool IsConnected() const { return Atomic::Load(m_connected); }
|
||||
|
||||
HANDLE NativeHandle();
|
||||
|
||||
void SetHandler(_In_opt_ IPipeEndpointHandler * handler)
|
||||
{
|
||||
m_handler = handler;
|
||||
|
||||
@@ -65,12 +65,14 @@
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CClipboardChannel.cpp" />
|
||||
<ClCompile Include="CClipboardRing.cpp" />
|
||||
<ClCompile Include="CDebug.cpp" />
|
||||
<ClCompile Include="CPipeEndpoint.cpp" />
|
||||
<ClCompile Include="RefreshRate.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CClipboardChannel.h" />
|
||||
<ClInclude Include="CClipboardRing.h" />
|
||||
<ClInclude Include="Atomic.h" />
|
||||
<ClInclude Include="CDebug.h" />
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CClipboardChannel.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CClipboardRing.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@@ -29,6 +32,9 @@
|
||||
<ClInclude Include="Atomic.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CClipboardChannel.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CClipboardRing.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
||||
Reference in New Issue
Block a user