mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[idd] clipboard: add direct synchronization
This commit is contained in:
@@ -31,6 +31,21 @@
|
||||
#define KVMFR_CLIPBOARD_DATA_BYTES (64U * 1024U)
|
||||
#define KVMFR_CLIPBOARD_SIZE_UNKNOWN UINT64_MAX
|
||||
|
||||
/* Transfer IDs are selected by the side which sends REQUEST. Keeping the
|
||||
* namespaces disjoint makes a simultaneous transfer in each direction
|
||||
* unambiguous when CANCEL and DATA cross in flight. */
|
||||
#define KVMFR_CLIPBOARD_TRANSFER_HELPER (UINT64_C(1) << 63)
|
||||
|
||||
static inline int kvmfrClipboardTransferFromHelper(uint64_t transfer)
|
||||
{
|
||||
return (transfer & KVMFR_CLIPBOARD_TRANSFER_HELPER) != 0;
|
||||
}
|
||||
|
||||
static inline int kvmfrClipboardTransferFromClient(uint64_t transfer)
|
||||
{
|
||||
return transfer != 0 && !kvmfrClipboardTransferFromHelper(transfer);
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_CLIPBOARD_FORMAT_NONE = 0,
|
||||
@@ -120,6 +135,14 @@ typedef struct KVMFRClipboardMessage
|
||||
}
|
||||
KVMFRClipboardMessage;
|
||||
|
||||
/* Type-specific fields:
|
||||
* OFFER: token is KVMFRClipboardFormatFlags.
|
||||
* REQUEST: format and transfer identify the requested representation.
|
||||
* DATA: size is an optional total hint on BEGIN and authoritative on END;
|
||||
* offset/length describe this record's borrowed payload.
|
||||
* CANCEL: token is a KVMFRClipboardCancelReason-compatible reason.
|
||||
* ACK/GRANT: token identifies the acknowledged or writable slot. */
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_CLIPBOARD_STATUS_AVAILABLE = 1U << 0,
|
||||
|
||||
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>
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
<ClCompile Include="postprocess\effect\CFormatEffect.cpp" />
|
||||
<ClCompile Include="postprocess\effect\CHDR16to10Effect.cpp" />
|
||||
<ClCompile Include="postprocess\effect\CRGB24Effect.cpp" />
|
||||
<ClCompile Include="transport\CClipboardHub.cpp" />
|
||||
<ClCompile Include="transport\CControlHub.cpp" />
|
||||
<ClCompile Include="transport\CFrameHub.cpp" />
|
||||
<ClCompile Include="transport\CInputHub.cpp" />
|
||||
@@ -76,6 +77,7 @@
|
||||
<ClCompile Include="transport\TransportFactory.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CIVSHMEM.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CRecovery.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPClipboardTransport.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPControl.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPFrameCaps.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPFrameTransport.cpp" />
|
||||
@@ -125,6 +127,7 @@
|
||||
<ClInclude Include="postprocess\effect\CFormatEffect.h" />
|
||||
<ClInclude Include="postprocess\effect\CHDR16to10Effect.h" />
|
||||
<ClInclude Include="postprocess\effect\CRGB24Effect.h" />
|
||||
<ClInclude Include="transport\CClipboardHub.h" />
|
||||
<ClInclude Include="transport\CControlHub.h" />
|
||||
<ClInclude Include="transport\CFrameHub.h" />
|
||||
<ClInclude Include="transport\CInputHub.h" />
|
||||
@@ -135,6 +138,7 @@
|
||||
<ClInclude Include="transport\FrameCaps.h" />
|
||||
<ClInclude Include="transport\FrameProfile.h" />
|
||||
<ClInclude Include="transport\FrameIn.h" />
|
||||
<ClInclude Include="transport\IClipboardSource.h" />
|
||||
<ClInclude Include="transport\IControlSink.h" />
|
||||
<ClInclude Include="transport\IControlTransport.h" />
|
||||
<ClInclude Include="transport\IFrameSink.h" />
|
||||
@@ -149,6 +153,7 @@
|
||||
<ClInclude Include="transport\TransportFactory.h" />
|
||||
<ClInclude Include="transport\lgmp\CIVSHMEM.h" />
|
||||
<ClInclude Include="transport\lgmp\CRecovery.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPClipboardTransport.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPControl.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPFrameCaps.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPFrameTransport.h" />
|
||||
|
||||
@@ -169,6 +169,9 @@
|
||||
<ClInclude Include="postprocess\effect\CRGB24Effect.h">
|
||||
<Filter>Post-processing\Effects</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\CClipboardHub.h">
|
||||
<Filter>Transport</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\CControlHub.h">
|
||||
<Filter>Transport</Filter>
|
||||
</ClInclude>
|
||||
@@ -199,6 +202,9 @@
|
||||
<ClInclude Include="transport\FrameIn.h">
|
||||
<Filter>Transport</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\IClipboardSource.h">
|
||||
<Filter>Transport</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\IControlSink.h">
|
||||
<Filter>Transport</Filter>
|
||||
</ClInclude>
|
||||
@@ -241,6 +247,9 @@
|
||||
<ClInclude Include="transport\lgmp\CRecovery.h">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\lgmp\CLGMPClipboardTransport.h">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\lgmp\CLGMPControl.h">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClInclude>
|
||||
@@ -369,6 +378,9 @@
|
||||
<ClCompile Include="postprocess\effect\CRGB24Effect.cpp">
|
||||
<Filter>Post-processing\Effects</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\CClipboardHub.cpp">
|
||||
<Filter>Transport</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\CControlHub.cpp">
|
||||
<Filter>Transport</Filter>
|
||||
</ClCompile>
|
||||
@@ -399,6 +411,9 @@
|
||||
<ClCompile Include="transport\lgmp\CRecovery.cpp">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\lgmp\CLGMPClipboardTransport.cpp">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\lgmp\CLGMPControl.cpp">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -37,6 +37,7 @@ bool CPipeServer::Init()
|
||||
void CPipeServer::DeInit()
|
||||
{
|
||||
m_endpoint.Stop();
|
||||
m_clipboard.Detach();
|
||||
}
|
||||
|
||||
void CPipeServer::OnPipeConnected()
|
||||
@@ -60,6 +61,11 @@ void CPipeServer::OnPipeConnected()
|
||||
m_endpoint.Send(&m_recoveryRequest, sizeof(m_recoveryRequest));
|
||||
}
|
||||
|
||||
void CPipeServer::OnPipeDisconnected()
|
||||
{
|
||||
m_clipboard.Detach();
|
||||
}
|
||||
|
||||
bool CPipeServer::OnPipeMessage(const void * message, size_t size)
|
||||
{
|
||||
if (size != sizeof(LGPipeMsg))
|
||||
@@ -82,12 +88,90 @@ bool CPipeServer::OnPipeMessage(const void * message, size_t size)
|
||||
HandleRecovery(msg);
|
||||
return true;
|
||||
|
||||
case LGPipeMsg::CLIPBOARD_SETUP:
|
||||
{
|
||||
HANDLE transferred = reinterpret_cast<HANDLE>(
|
||||
static_cast<uintptr_t>(msg.clipboardSetup.handle));
|
||||
HANDLE mapping = transferred;
|
||||
uint64_t epoch = 0;
|
||||
if (transferred &&
|
||||
msg.clipboardSetup.bytes == sizeof(ClipboardMapping))
|
||||
{
|
||||
ClipboardMapping * view = static_cast<ClipboardMapping *>(
|
||||
MapViewOfFile(mapping, FILE_MAP_READ, 0, 0,
|
||||
sizeof(ClipboardMapping)));
|
||||
if (view)
|
||||
{
|
||||
epoch = view->epoch;
|
||||
UnmapViewOfFile(view);
|
||||
}
|
||||
}
|
||||
else if (transferred)
|
||||
{
|
||||
CloseHandle(transferred);
|
||||
mapping = nullptr;
|
||||
}
|
||||
|
||||
// The transferred handle has exactly one owner from this point:
|
||||
// Attach consumes it on both success and failure paths.
|
||||
const bool ready = m_clipboard.Attach(
|
||||
mapping, epoch, false, *this);
|
||||
LGPipeMsg reply = {};
|
||||
reply.size = sizeof(reply);
|
||||
reply.type = LGPipeMsg::CLIPBOARD_READY;
|
||||
reply.clipboardReady.epoch = epoch;
|
||||
reply.clipboardReady.status = ready ? ERROR_SUCCESS : ERROR_INVALID_DATA;
|
||||
m_endpoint.Send(&reply, sizeof(reply));
|
||||
return true;
|
||||
}
|
||||
|
||||
case LGPipeMsg::CLIPBOARD_READY:
|
||||
// READY normally travels IDD to Helper. A failure in the reverse
|
||||
// direction reports that Helper activation failed after IDD attach.
|
||||
if (msg.clipboardReady.status != ERROR_SUCCESS &&
|
||||
msg.clipboardReady.epoch == m_clipboard.Epoch())
|
||||
{
|
||||
m_clipboard.Reset(
|
||||
msg.clipboardReady.epoch, msg.clipboardReady.status);
|
||||
m_clipboard.Detach();
|
||||
}
|
||||
return true;
|
||||
|
||||
case LGPipeMsg::CLIPBOARD_KICK:
|
||||
m_clipboard.Kick(msg.clipboardKick.epoch);
|
||||
return true;
|
||||
|
||||
case LGPipeMsg::CLIPBOARD_RESET:
|
||||
m_clipboard.Reset(
|
||||
msg.clipboardReset.epoch, msg.clipboardReset.reason);
|
||||
return true;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unknown message type %d", msg.type);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CPipeServer::ClipboardKick(uint64_t epoch)
|
||||
{
|
||||
LGPipeMsg msg = {};
|
||||
msg.size = sizeof(msg);
|
||||
msg.type = LGPipeMsg::CLIPBOARD_KICK;
|
||||
msg.clipboardKick.epoch = epoch;
|
||||
msg.clipboardKick.rings = 0;
|
||||
return m_endpoint.Send(&msg, sizeof(msg));
|
||||
}
|
||||
|
||||
void CPipeServer::ClipboardResetPeer(uint64_t epoch, uint32_t reason)
|
||||
{
|
||||
LGPipeMsg msg = {};
|
||||
msg.size = sizeof(msg);
|
||||
msg.type = LGPipeMsg::CLIPBOARD_RESET;
|
||||
msg.clipboardReset.epoch = epoch;
|
||||
msg.clipboardReset.reason = reason;
|
||||
m_endpoint.Send(&msg, sizeof(msg));
|
||||
}
|
||||
|
||||
void CPipeServer::QueueMsgLocked(const LGPipeMsg & msg)
|
||||
{
|
||||
for (LGPipeMsg & queued : m_queue)
|
||||
|
||||
@@ -26,12 +26,14 @@
|
||||
#include <vector>
|
||||
|
||||
#include "CPipeEndpoint.h"
|
||||
#include "CClipboardChannel.h"
|
||||
#include "CSRWLock.h"
|
||||
#include "PipeMsg.h"
|
||||
|
||||
class CDeviceContext;
|
||||
|
||||
class CPipeServer : private IPipeEndpointHandler
|
||||
class CPipeServer : private IPipeEndpointHandler,
|
||||
public IClipboardChannelDoorbell
|
||||
{
|
||||
public:
|
||||
using RecoveryHandler = void (*)(void * opaque,
|
||||
@@ -40,6 +42,7 @@ class CPipeServer : private IPipeEndpointHandler
|
||||
|
||||
private:
|
||||
CPipeEndpoint m_endpoint;
|
||||
CClipboardChannel m_clipboard;
|
||||
CSRWLock m_queueLock;
|
||||
std::vector<LGPipeMsg> m_queue;
|
||||
bool m_recoveryValid = false;
|
||||
@@ -60,6 +63,7 @@ class CPipeServer : private IPipeEndpointHandler
|
||||
void HandleRecovery(const LGPipeMsg & msg);
|
||||
|
||||
void OnPipeConnected() override;
|
||||
void OnPipeDisconnected() override;
|
||||
bool OnPipeMessage(const void * message, size_t size) override;
|
||||
|
||||
public:
|
||||
@@ -80,6 +84,10 @@ class CPipeServer : private IPipeEndpointHandler
|
||||
uint32_t requiredSizeMiB);
|
||||
bool SetRecovery(void * owner, uint64_t route,
|
||||
uint64_t session, uint32_t serial, bool active);
|
||||
|
||||
CClipboardChannel& Clipboard() { return m_clipboard; }
|
||||
bool ClipboardKick(uint64_t epoch) override;
|
||||
void ClipboardResetPeer(uint64_t epoch, uint32_t reason) override;
|
||||
};
|
||||
|
||||
extern CPipeServer g_pipe;
|
||||
|
||||
380
idd/LGIdd/transport/CClipboardHub.cpp
Normal file
380
idd/LGIdd/transport/CClipboardHub.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
/**
|
||||
* 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 "transport/CClipboardHub.h"
|
||||
|
||||
#include "Seq.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool ValidHelperDirection(const KVMFRClipboardMessage& record)
|
||||
{
|
||||
if (record.type == KVMFR_CLIPBOARD_MESSAGE_REQUEST)
|
||||
return kvmfrClipboardTransferFromHelper(record.transfer);
|
||||
if (record.type == KVMFR_CLIPBOARD_MESSAGE_DATA)
|
||||
return kvmfrClipboardTransferFromClient(record.transfer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
CClipboardHub::CClipboardHub(CClipboardChannel& channel) :
|
||||
m_channel(channel)
|
||||
{
|
||||
m_channel.SetHandler(this);
|
||||
}
|
||||
|
||||
CClipboardHub::~CClipboardHub()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void CClipboardHub::AdvanceGenerationNL()
|
||||
{
|
||||
Seq::Inc(m_generation);
|
||||
}
|
||||
|
||||
bool CClipboardHub::MarkFailed(IClipboardSource& source)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_source != &source || (!m_active && !m_reserved))
|
||||
return false;
|
||||
|
||||
const bool changed = !m_failed;
|
||||
m_failed = true;
|
||||
m_failurePending = true;
|
||||
return changed;
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardHub::HoldOrDiscard(
|
||||
const KVMFRClipboardMessage& record)
|
||||
{
|
||||
// Preserve the pending clipboard publication until a source is rebound.
|
||||
// Transfer-scoped records belong to the source which admitted them and
|
||||
// must never cross a transport generation.
|
||||
if (record.type == KVMFR_CLIPBOARD_MESSAGE_OFFER ||
|
||||
record.type == KVMFR_CLIPBOARD_MESSAGE_CLEAR)
|
||||
return ClipboardChannelResult::BUSY;
|
||||
|
||||
if (record.type == KVMFR_CLIPBOARD_MESSAGE_REQUEST)
|
||||
{
|
||||
KVMFRClipboardMessage cancel = {};
|
||||
cancel.version = KVMFR_CLIPBOARD_VERSION;
|
||||
cancel.type = KVMFR_CLIPBOARD_MESSAGE_CANCEL;
|
||||
cancel.generation = record.generation;
|
||||
cancel.clipboardGeneration = record.clipboardGeneration;
|
||||
cancel.transfer = record.transfer;
|
||||
cancel.format = record.format;
|
||||
cancel.token = ERROR_DEVICE_NOT_CONNECTED;
|
||||
const ClipboardChannelResult result = m_channel.Send(cancel);
|
||||
if (result != ClipboardChannelResult::ACCEPTED)
|
||||
return result;
|
||||
}
|
||||
|
||||
return ClipboardChannelResult::ACCEPTED;
|
||||
}
|
||||
|
||||
bool CClipboardHub::Bind(
|
||||
BackendId backend, uint32_t epoch, IClipboardSource& source)
|
||||
{
|
||||
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||
if (!backend || !epoch)
|
||||
return false;
|
||||
|
||||
bool available;
|
||||
uint32_t generation;
|
||||
uint64_t channelEpoch;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_stopped || m_source || m_active || m_reserved)
|
||||
return false;
|
||||
|
||||
AdvanceGenerationNL();
|
||||
m_source = &source;
|
||||
m_backend = backend;
|
||||
m_epoch = epoch;
|
||||
m_active = true;
|
||||
m_reserved = true;
|
||||
m_failed = false;
|
||||
m_failurePending = false;
|
||||
available = m_available;
|
||||
generation = m_generation;
|
||||
channelEpoch = m_channelEpoch;
|
||||
}
|
||||
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
const bool started = source.Start(*this);
|
||||
bool attached = false;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (started && m_source == &source && m_backend == backend &&
|
||||
m_epoch == epoch && m_active && m_reserved && !m_failed &&
|
||||
!m_stopped)
|
||||
{
|
||||
m_running = true;
|
||||
m_reserved = false;
|
||||
available = m_available;
|
||||
generation = m_generation;
|
||||
channelEpoch = m_channelEpoch;
|
||||
attached = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_active = false;
|
||||
m_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!attached)
|
||||
{
|
||||
source.Stop();
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_source == &source && m_backend == backend && m_epoch == epoch)
|
||||
{
|
||||
m_source = nullptr;
|
||||
m_backend = 0;
|
||||
m_epoch = 0;
|
||||
m_active = false;
|
||||
m_running = false;
|
||||
m_reserved = false;
|
||||
m_failed = false;
|
||||
m_failurePending = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
source.ClipboardState(available, generation);
|
||||
callbackLock.Unlock();
|
||||
if (available && channelEpoch)
|
||||
m_channel.Kick(channelEpoch);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CClipboardHub::Unbind(BackendId backend, uint32_t epoch)
|
||||
{
|
||||
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||
IClipboardSource * source = nullptr;
|
||||
bool stop = false;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if ((!m_active && !m_failed && !m_reserved) ||
|
||||
m_backend != backend || m_epoch != epoch || !m_source)
|
||||
return;
|
||||
|
||||
source = m_source;
|
||||
stop = m_running || m_reserved;
|
||||
m_active = false;
|
||||
m_running = false;
|
||||
m_reserved = true;
|
||||
m_failed = false;
|
||||
m_failurePending = false;
|
||||
AdvanceGenerationNL();
|
||||
}
|
||||
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
if (stop)
|
||||
source->Stop();
|
||||
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_source == source && m_backend == backend && m_epoch == epoch)
|
||||
{
|
||||
m_source = nullptr;
|
||||
m_backend = 0;
|
||||
m_epoch = 0;
|
||||
m_active = false;
|
||||
m_running = false;
|
||||
m_reserved = false;
|
||||
m_failed = false;
|
||||
m_failurePending = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CClipboardHub::TakeFailure(SourceKey& source)
|
||||
{
|
||||
CSRWSharedLock lifecycleLock(m_lifecycleLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!m_failurePending)
|
||||
return false;
|
||||
|
||||
source = {};
|
||||
source.backend = m_backend;
|
||||
source.epoch = m_epoch;
|
||||
m_failurePending = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CClipboardHub::Stop()
|
||||
{
|
||||
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||
IClipboardSource * source = nullptr;
|
||||
bool stop = false;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_stopped)
|
||||
return;
|
||||
|
||||
m_stopped = true;
|
||||
source = m_source;
|
||||
stop = source && (m_running || m_reserved);
|
||||
m_active = false;
|
||||
m_running = false;
|
||||
m_reserved = source != nullptr;
|
||||
m_failed = false;
|
||||
m_failurePending = false;
|
||||
AdvanceGenerationNL();
|
||||
}
|
||||
|
||||
// Clearing the channel handler is a callback-quiescence barrier. It must
|
||||
// precede taking m_callbackLock because an in-flight channel callback owns
|
||||
// the channel's handler lock while waiting for m_callbackLock.
|
||||
m_channel.ClearHandler(this);
|
||||
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
if (stop)
|
||||
source->Stop();
|
||||
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
m_source = nullptr;
|
||||
m_backend = 0;
|
||||
m_epoch = 0;
|
||||
m_active = false;
|
||||
m_running = false;
|
||||
m_reserved = false;
|
||||
m_failed = false;
|
||||
m_failurePending = false;
|
||||
}
|
||||
|
||||
void CClipboardHub::ClipboardState(bool available, uint64_t epoch)
|
||||
{
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
IClipboardSource * source = nullptr;
|
||||
uint32_t generation;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_stopped)
|
||||
return;
|
||||
|
||||
if (m_available != available || m_channelEpoch != epoch)
|
||||
AdvanceGenerationNL();
|
||||
m_available = available;
|
||||
m_channelEpoch = epoch;
|
||||
generation = m_generation;
|
||||
if (m_active && m_running && !m_failed)
|
||||
source = m_source;
|
||||
}
|
||||
|
||||
if (source)
|
||||
source->ClipboardState(available, generation);
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardHub::ClipboardRecord(
|
||||
const KVMFRClipboardMessage& record, const uint8_t * data)
|
||||
{
|
||||
if (!ValidHelperDirection(record))
|
||||
return ClipboardChannelResult::FAILED;
|
||||
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
IClipboardSource * source = nullptr;
|
||||
uint32_t generation;
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
if (m_stopped || !m_available)
|
||||
return ClipboardChannelResult::ACCEPTED;
|
||||
if (m_failed || !m_active || !m_running || !m_source)
|
||||
return HoldOrDiscard(record);
|
||||
source = m_source;
|
||||
generation = m_generation;
|
||||
}
|
||||
|
||||
KVMFRClipboardMessage stamped = record;
|
||||
stamped.generation = generation;
|
||||
const ClipboardChannelResult result =
|
||||
source->SendClipboard(stamped, data);
|
||||
if (result != ClipboardChannelResult::FAILED)
|
||||
return result;
|
||||
|
||||
if (MarkFailed(*source))
|
||||
{
|
||||
const uint64_t epoch = m_channel.Epoch();
|
||||
if (epoch)
|
||||
m_channel.Kick(epoch);
|
||||
}
|
||||
return HoldOrDiscard(record);
|
||||
}
|
||||
|
||||
void CClipboardHub::ClipboardReset(uint64_t epoch, uint32_t reason)
|
||||
{
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
IClipboardSource * source = nullptr;
|
||||
uint32_t generation;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_stopped || m_channelEpoch != epoch)
|
||||
return;
|
||||
|
||||
AdvanceGenerationNL();
|
||||
generation = m_generation;
|
||||
if (m_active && m_running && !m_failed)
|
||||
source = m_source;
|
||||
}
|
||||
|
||||
if (source)
|
||||
source->ClipboardReset(generation, reason);
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardHub::SendClipboard(
|
||||
const KVMFRClipboardMessage& record, const uint8_t * data)
|
||||
{
|
||||
// Keep the shared lock through Send so Unbind cannot advance the binding
|
||||
// generation after validation but before the record enters the channel.
|
||||
CSRWSharedLock lock(m_lock);
|
||||
if (m_stopped || !m_available || !m_active || !m_running || m_failed ||
|
||||
!m_source || record.generation != m_generation)
|
||||
return ClipboardChannelResult::FAILED;
|
||||
return m_channel.Send(record, data);
|
||||
}
|
||||
|
||||
void CClipboardHub::ClipboardReceiveReady()
|
||||
{
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
if (m_stopped || !m_available || !m_active || m_failed ||
|
||||
!m_running)
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t epoch = m_channel.Epoch();
|
||||
if (epoch)
|
||||
m_channel.Kick(epoch);
|
||||
}
|
||||
|
||||
void CClipboardHub::ClipboardFailed()
|
||||
{
|
||||
IClipboardSource * source;
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
source = m_source;
|
||||
}
|
||||
if (!source || !MarkFailed(*source))
|
||||
return;
|
||||
|
||||
const uint64_t epoch = m_channel.Epoch();
|
||||
if (epoch)
|
||||
m_channel.Kick(epoch);
|
||||
}
|
||||
78
idd/LGIdd/transport/CClipboardHub.h
Normal file
78
idd/LGIdd/transport/CClipboardHub.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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 "CClipboardChannel.h"
|
||||
#include "CSRWLock.h"
|
||||
#include "transport/IClipboardSource.h"
|
||||
#include "transport/ITransport.h"
|
||||
|
||||
class CClipboardHub final : public IClipboardTarget,
|
||||
private IClipboardChannelHandler
|
||||
{
|
||||
private:
|
||||
CClipboardChannel& m_channel;
|
||||
CSRWLock m_lifecycleLock;
|
||||
CSRWLock m_callbackLock;
|
||||
CSRWLock m_lock;
|
||||
|
||||
IClipboardSource * m_source = nullptr;
|
||||
BackendId m_backend = 0;
|
||||
uint32_t m_epoch = 0;
|
||||
uint64_t m_channelEpoch = 0;
|
||||
uint32_t m_generation = 1;
|
||||
bool m_available = false;
|
||||
bool m_active = false;
|
||||
bool m_running = false;
|
||||
bool m_reserved = false;
|
||||
bool m_failed = false;
|
||||
bool m_failurePending = false;
|
||||
bool m_stopped = false;
|
||||
|
||||
void AdvanceGenerationNL();
|
||||
bool MarkFailed(IClipboardSource& source);
|
||||
ClipboardChannelResult HoldOrDiscard(
|
||||
const KVMFRClipboardMessage& record);
|
||||
|
||||
void ClipboardState(bool available, uint64_t epoch) override;
|
||||
ClipboardChannelResult ClipboardRecord(
|
||||
const KVMFRClipboardMessage& record,
|
||||
const uint8_t * data) override;
|
||||
void ClipboardReset(uint64_t epoch, uint32_t reason) override;
|
||||
|
||||
public:
|
||||
explicit CClipboardHub(CClipboardChannel& channel);
|
||||
~CClipboardHub() override;
|
||||
|
||||
CClipboardHub(const CClipboardHub&) = delete;
|
||||
CClipboardHub& operator=(const CClipboardHub&) = delete;
|
||||
|
||||
bool Bind(BackendId backend, uint32_t epoch, IClipboardSource& source);
|
||||
void Unbind(BackendId backend, uint32_t epoch);
|
||||
bool TakeFailure(SourceKey& source);
|
||||
void Stop();
|
||||
|
||||
ClipboardChannelResult SendClipboard(
|
||||
const KVMFRClipboardMessage& record,
|
||||
const uint8_t * data) override;
|
||||
void ClipboardReceiveReady() override;
|
||||
void ClipboardFailed() override;
|
||||
};
|
||||
@@ -23,7 +23,9 @@
|
||||
#include "Atomic.h"
|
||||
#include "capture/CFrameGraph.h"
|
||||
#include "CDebug.h"
|
||||
#include "ipc/CPipeServer.h"
|
||||
#include "Seq.h"
|
||||
#include "transport/IClipboardSource.h"
|
||||
#include "transport/ITexStage.h"
|
||||
|
||||
#include <Windows.h>
|
||||
@@ -121,7 +123,8 @@ CTransportManager::Entry::~Entry()
|
||||
CloseHandle(idleEvent);
|
||||
}
|
||||
|
||||
CTransportManager::CTransportManager() : m_tex(m_frameRev)
|
||||
CTransportManager::CTransportManager() :
|
||||
m_tex(m_frameRev), m_clipboard(g_pipe.Clipboard())
|
||||
{
|
||||
m_phaseIdle = CreateEvent(nullptr, TRUE, TRUE, nullptr);
|
||||
m_stoppedEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
@@ -492,48 +495,55 @@ bool CTransportManager::InitializeEntry(Entry& entry)
|
||||
bool CTransportManager::AddServices(Entry& entry)
|
||||
{
|
||||
std::shared_ptr<ITransport> transport;
|
||||
BackendId id = 0;
|
||||
uint32_t epoch = 0;
|
||||
bool primary = false;
|
||||
bool required = false;
|
||||
uint32_t services = 0;
|
||||
bool controlAdded = false;
|
||||
bool controlFailed = false;
|
||||
bool controlAbsent = false;
|
||||
bool inputAdded = false;
|
||||
bool inputFailed = false;
|
||||
bool inputAbsent = false;
|
||||
bool frameAdded = false;
|
||||
bool frameBound = false;
|
||||
bool frameAbsent = false;
|
||||
uint64_t retryAt = 0;
|
||||
BackendId id = 0;
|
||||
uint32_t epoch = 0;
|
||||
bool primary = false;
|
||||
bool required = false;
|
||||
uint32_t services = 0;
|
||||
bool controlAdded = false;
|
||||
bool controlFailed = false;
|
||||
bool controlAbsent = false;
|
||||
bool inputAdded = false;
|
||||
bool inputFailed = false;
|
||||
bool inputAbsent = false;
|
||||
bool clipboardAdded = false;
|
||||
bool clipboardFailed = false;
|
||||
bool clipboardAbsent = false;
|
||||
bool frameAdded = false;
|
||||
bool frameBound = false;
|
||||
bool frameAbsent = false;
|
||||
uint64_t retryAt = 0;
|
||||
{
|
||||
CSRWSharedLock entryLock(entry.lock);
|
||||
transport = entry.transport;
|
||||
id = entry.id;
|
||||
epoch = entry.epoch;
|
||||
primary = entry.primary;
|
||||
required = entry.required;
|
||||
services = entry.config.services;
|
||||
controlAdded = entry.controlAdded;
|
||||
controlFailed = entry.controlFailed;
|
||||
controlAbsent = entry.controlAbsent;
|
||||
inputAdded = entry.inputAdded;
|
||||
inputFailed = entry.inputFailed;
|
||||
inputAbsent = entry.inputAbsent;
|
||||
frameAdded = entry.frameAdded;
|
||||
frameAbsent = entry.frameAbsent;
|
||||
retryAt = entry.serviceRetryAt;
|
||||
transport = entry.transport;
|
||||
id = entry.id;
|
||||
epoch = entry.epoch;
|
||||
primary = entry.primary;
|
||||
required = entry.required;
|
||||
services = entry.config.services;
|
||||
controlAdded = entry.controlAdded;
|
||||
controlFailed = entry.controlFailed;
|
||||
controlAbsent = entry.controlAbsent;
|
||||
inputAdded = entry.inputAdded;
|
||||
inputFailed = entry.inputFailed;
|
||||
inputAbsent = entry.inputAbsent;
|
||||
clipboardAdded = entry.clipboardAdded;
|
||||
clipboardFailed = entry.clipboardFailed;
|
||||
clipboardAbsent = entry.clipboardAbsent;
|
||||
frameAdded = entry.frameAdded;
|
||||
frameAbsent = entry.frameAbsent;
|
||||
retryAt = entry.serviceRetryAt;
|
||||
}
|
||||
|
||||
if (!transport)
|
||||
return !required;
|
||||
|
||||
const uint64_t now = GetTickCount64();
|
||||
const bool attach = now >= retryAt;
|
||||
bool controlRetry = false;
|
||||
bool frameRetry = false;
|
||||
bool inputRetry = false;
|
||||
const uint64_t now = GetTickCount64();
|
||||
const bool attach = now >= retryAt;
|
||||
bool controlRetry = false;
|
||||
bool frameRetry = false;
|
||||
bool inputRetry = false;
|
||||
bool clipboardRetry = false;
|
||||
if (!(services & TRANSPORT_SERVICE_CONTROL))
|
||||
controlAbsent = true;
|
||||
else if (attach && !controlAdded && !controlFailed && !controlAbsent)
|
||||
@@ -610,7 +620,30 @@ bool CTransportManager::AddServices(Entry& entry)
|
||||
}
|
||||
}
|
||||
|
||||
if (attach && (controlRetry || inputRetry || frameRetry))
|
||||
if (!(services & TRANSPORT_SERVICE_CLIPBOARD))
|
||||
clipboardAbsent = true;
|
||||
else if (attach && !clipboardAdded && !clipboardFailed &&
|
||||
!clipboardAbsent)
|
||||
{
|
||||
IClipboardSource * clipboard = transport->Clipboard();
|
||||
if (clipboard && m_clipboard.Bind(id, epoch, *clipboard))
|
||||
{
|
||||
CSRWExclusiveLock entryLock(entry.lock);
|
||||
entry.clipboardAdded = true;
|
||||
clipboardAdded = true;
|
||||
}
|
||||
else if (clipboard)
|
||||
clipboardRetry = true;
|
||||
else
|
||||
{
|
||||
clipboardAbsent = true;
|
||||
CSRWExclusiveLock entryLock(entry.lock);
|
||||
entry.clipboardAbsent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (attach && (controlRetry || inputRetry || frameRetry ||
|
||||
clipboardRetry))
|
||||
{
|
||||
CSRWExclusiveLock entryLock(entry.lock);
|
||||
entry.serviceRetryAt = now + SERVICE_RETRY_DELAY_MS;
|
||||
@@ -623,9 +656,10 @@ bool CTransportManager::AddServices(Entry& entry)
|
||||
}
|
||||
|
||||
const bool servicesReady =
|
||||
(!(services & TRANSPORT_SERVICE_FRAME) || frameAdded) &&
|
||||
(!(services & TRANSPORT_SERVICE_CONTROL) || controlAdded) &&
|
||||
(!(services & TRANSPORT_SERVICE_INPUT) || inputAdded);
|
||||
(!(services & TRANSPORT_SERVICE_FRAME) || frameAdded) &&
|
||||
(!(services & TRANSPORT_SERVICE_CONTROL) || controlAdded) &&
|
||||
(!(services & TRANSPORT_SERVICE_INPUT) || inputAdded) &&
|
||||
(!(services & TRANSPORT_SERVICE_CLIPBOARD) || clipboardAdded);
|
||||
return !required || servicesReady;
|
||||
}
|
||||
|
||||
@@ -720,6 +754,35 @@ void CTransportManager::HandleServiceFailures()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
source = {};
|
||||
while (m_clipboard.TakeFailure(source))
|
||||
{
|
||||
Entry * entries[FRAME_MAX_SINKS] = {};
|
||||
const unsigned count = Entries(entries);
|
||||
for (unsigned i = 0; i < count; ++i)
|
||||
{
|
||||
Entry& entry = *entries[i];
|
||||
bool restart = false;
|
||||
{
|
||||
CSRWExclusiveLock entryLock(entry.lock);
|
||||
if (entry.id != source.backend || entry.epoch != source.epoch ||
|
||||
!entry.clipboardAdded)
|
||||
continue;
|
||||
entry.clipboardAdded = false;
|
||||
entry.clipboardFailed = true;
|
||||
restart = !entry.exposed;
|
||||
}
|
||||
|
||||
m_clipboard.Unbind(source.backend, source.epoch);
|
||||
if (restart)
|
||||
{
|
||||
RemoveServices(entry);
|
||||
ScheduleRetry(entry);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CTransportManager::SetupEntry(Entry& entry, size_t alignment)
|
||||
@@ -770,12 +833,13 @@ void CTransportManager::ScheduleRetry(Entry& entry)
|
||||
|
||||
void CTransportManager::RemoveServices(Entry& entry)
|
||||
{
|
||||
BackendId id = 0;
|
||||
uint32_t epoch = 0;
|
||||
bool frameAdded = false;
|
||||
bool frameLegacy = false;
|
||||
bool controlAdded = false;
|
||||
bool inputAdded = false;
|
||||
BackendId id = 0;
|
||||
uint32_t epoch = 0;
|
||||
bool frameAdded = false;
|
||||
bool frameLegacy = false;
|
||||
bool controlAdded = false;
|
||||
bool inputAdded = false;
|
||||
bool clipboardAdded = false;
|
||||
{
|
||||
CSRWExclusiveLock entryLock(entry.lock);
|
||||
id = entry.id;
|
||||
@@ -784,11 +848,13 @@ void CTransportManager::RemoveServices(Entry& entry)
|
||||
frameLegacy = entry.frameLegacy;
|
||||
controlAdded = entry.controlAdded;
|
||||
inputAdded = entry.inputAdded;
|
||||
clipboardAdded = entry.clipboardAdded;
|
||||
entry.frameAdded = false;
|
||||
entry.frameLegacy = false;
|
||||
entry.texSink = nullptr;
|
||||
entry.controlAdded = false;
|
||||
entry.inputAdded = false;
|
||||
entry.clipboardAdded = false;
|
||||
entry.frameRetryAt = 0;
|
||||
}
|
||||
|
||||
@@ -796,6 +862,8 @@ void CTransportManager::RemoveServices(Entry& entry)
|
||||
m_tex.Drop(id, epoch);
|
||||
if (inputAdded)
|
||||
m_input.Unbind(id, epoch);
|
||||
if (clipboardAdded)
|
||||
m_clipboard.Unbind(id, epoch);
|
||||
if (controlAdded)
|
||||
m_control.Remove(id, epoch);
|
||||
if (frameLegacy)
|
||||
@@ -835,6 +903,8 @@ void CTransportManager::RetryEntry(Entry& entry, uint64_t now,
|
||||
entry.controlAbsent = false;
|
||||
entry.inputFailed = false;
|
||||
entry.inputAbsent = false;
|
||||
entry.clipboardFailed = false;
|
||||
entry.clipboardAbsent = false;
|
||||
entry.frameAbsent = false;
|
||||
entry.serviceRetryAt = 0;
|
||||
Seq::Inc(entry.epoch);
|
||||
@@ -1601,6 +1671,7 @@ void CTransportManager::Stop()
|
||||
}
|
||||
|
||||
m_input.Stop();
|
||||
m_clipboard.Stop();
|
||||
|
||||
for (unsigned i = count; i > 0; --i)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "Atomic.h"
|
||||
#include "CSRWLock.h"
|
||||
#include "transport/CClipboardHub.h"
|
||||
#include "transport/CControlHub.h"
|
||||
#include "transport/CFrameHub.h"
|
||||
#include "transport/CInputHub.h"
|
||||
@@ -109,6 +110,9 @@ private:
|
||||
bool inputAdded = false;
|
||||
bool inputFailed = false;
|
||||
bool inputAbsent = false;
|
||||
bool clipboardAdded = false;
|
||||
bool clipboardFailed = false;
|
||||
bool clipboardAbsent = false;
|
||||
bool frameAdded = false;
|
||||
bool frameLegacy = false;
|
||||
ITexSink * texSink = nullptr;
|
||||
@@ -132,6 +136,7 @@ private:
|
||||
CFrameHub m_frames;
|
||||
CTexHub m_tex;
|
||||
CInputHub m_input;
|
||||
CClipboardHub m_clipboard;
|
||||
CRecoveryHub m_recovery;
|
||||
Entry * m_primary = nullptr;
|
||||
bool m_initialized = false;
|
||||
|
||||
77
idd/LGIdd/transport/IClipboardSource.h
Normal file
77
idd/LGIdd/transport/IClipboardSource.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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 "CClipboardChannel.h"
|
||||
#include "common/KVMFRClipboard.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class IClipboardTarget
|
||||
{
|
||||
public:
|
||||
virtual ~IClipboardTarget() = default;
|
||||
|
||||
// Sends one client-to-Helper record. record.generation must be the binding
|
||||
// generation most recently supplied by ClipboardState; transports which
|
||||
// expose their own endpoint generation validate and replace it at this
|
||||
// adapter boundary. clipboardGeneration remains the clipboard content
|
||||
// generation and is preserved end-to-end.
|
||||
//
|
||||
// BUSY consumes neither the record nor its borrowed data; the source
|
||||
// retains and retries the exact record from its bounded worker/Process
|
||||
// drain.
|
||||
virtual ClipboardChannelResult SendClipboard(
|
||||
const KVMFRClipboardMessage& record, const uint8_t * data) = 0;
|
||||
|
||||
// The source calls ClipboardReceiveReady after a Helper-to-client record
|
||||
// returned BUSY and it can accept an exact retry of that record.
|
||||
virtual void ClipboardReceiveReady() = 0;
|
||||
virtual void ClipboardFailed() = 0;
|
||||
};
|
||||
|
||||
class IClipboardSource
|
||||
{
|
||||
public:
|
||||
virtual ~IClipboardSource() = default;
|
||||
|
||||
// Start attaches the target, but the source must wait for the initial
|
||||
// ClipboardState callback before calling target.SendClipboard. Stop is a
|
||||
// callback-quiescence barrier and leaves no work which can access target;
|
||||
// it also cleans up a partially completed or failed Start.
|
||||
virtual bool Start(IClipboardTarget& target) = 0;
|
||||
virtual void Stop() = 0;
|
||||
|
||||
// All callbacks into the source, including Start and Stop, are serialized.
|
||||
// record.generation is stamped with the current binding generation. A
|
||||
// generation change invalidates records from an earlier generation and
|
||||
// cancels their transfers; clipboardGeneration is unaffected. data is
|
||||
// borrowed only for the duration of SendClipboard. BUSY consumes neither
|
||||
// record nor data, and the channel retries the same record after
|
||||
// ClipboardReceiveReady (or its bounded polling fallback).
|
||||
virtual void ClipboardState(bool available, uint32_t generation) = 0;
|
||||
virtual ClipboardChannelResult SendClipboard(
|
||||
const KVMFRClipboardMessage& record, const uint8_t * data) = 0;
|
||||
|
||||
// Reset advances generation before the callback. The source cancels all
|
||||
// transfers from the preceding generation.
|
||||
virtual void ClipboardReset(uint32_t generation, uint32_t reason) = 0;
|
||||
};
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
class IClipboardSource;
|
||||
class IControlSink;
|
||||
class IFrameSink;
|
||||
class IInputSource;
|
||||
@@ -178,4 +179,5 @@ public:
|
||||
virtual ITexSink * TexSink() { return nullptr; }
|
||||
virtual IControlSink * Control() { return nullptr; }
|
||||
virtual IInputSource * Input() { return nullptr; }
|
||||
virtual IClipboardSource * Clipboard() { return nullptr; }
|
||||
};
|
||||
|
||||
@@ -125,6 +125,8 @@ namespace
|
||||
service = TRANSPORT_SERVICE_CONTROL;
|
||||
else if (Equal(name, L"input"))
|
||||
service = TRANSPORT_SERVICE_INPUT;
|
||||
else if (Equal(name, L"clipboard"))
|
||||
service = TRANSPORT_SERVICE_CLIPBOARD;
|
||||
else
|
||||
return false;
|
||||
|
||||
|
||||
@@ -28,11 +28,13 @@ using BackendId = uint32_t;
|
||||
|
||||
enum TransportService : uint32_t
|
||||
{
|
||||
TRANSPORT_SERVICE_FRAME = 1U << 0,
|
||||
TRANSPORT_SERVICE_CONTROL = 1U << 1,
|
||||
TRANSPORT_SERVICE_INPUT = 1U << 2,
|
||||
TRANSPORT_SERVICE_ALL = TRANSPORT_SERVICE_FRAME |
|
||||
TRANSPORT_SERVICE_CONTROL | TRANSPORT_SERVICE_INPUT,
|
||||
TRANSPORT_SERVICE_FRAME = 1U << 0,
|
||||
TRANSPORT_SERVICE_CONTROL = 1U << 1,
|
||||
TRANSPORT_SERVICE_INPUT = 1U << 2,
|
||||
TRANSPORT_SERVICE_CLIPBOARD = 1U << 3,
|
||||
TRANSPORT_SERVICE_ALL = TRANSPORT_SERVICE_FRAME |
|
||||
TRANSPORT_SERVICE_CONTROL | TRANSPORT_SERVICE_INPUT |
|
||||
TRANSPORT_SERVICE_CLIPBOARD,
|
||||
};
|
||||
|
||||
struct TransportInstance
|
||||
|
||||
1434
idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.cpp
Normal file
1434
idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.cpp
Normal file
File diff suppressed because it is too large
Load Diff
211
idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h
Normal file
211
idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h
Normal 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;
|
||||
};
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
2294
idd/LGIddHelper/CClipboardManager.cpp
Normal file
2294
idd/LGIddHelper/CClipboardManager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
231
idd/LGIddHelper/CClipboardManager.h
Normal file
231
idd/LGIddHelper/CClipboardManager.h
Normal file
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* 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 "CClipboardChannel.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
class CClipboardSpool;
|
||||
|
||||
class CClipboardManager final : private IClipboardChannelHandler
|
||||
{
|
||||
private:
|
||||
enum class WorkType
|
||||
{
|
||||
STATE,
|
||||
RESET,
|
||||
RECORD,
|
||||
SEND,
|
||||
SEND_DATA,
|
||||
};
|
||||
|
||||
enum class UIType
|
||||
{
|
||||
STATE,
|
||||
OFFER,
|
||||
CLEAR,
|
||||
REQUEST,
|
||||
};
|
||||
|
||||
struct Work
|
||||
{
|
||||
WorkType type = WorkType::STATE;
|
||||
bool available = false;
|
||||
uint64_t epoch = 0;
|
||||
uint32_t reason = 0;
|
||||
KVMFRClipboardMessage record = {};
|
||||
std::vector<uint8_t> data;
|
||||
std::shared_ptr<CClipboardSpool> spool;
|
||||
uint64_t deadline = 0;
|
||||
};
|
||||
|
||||
struct UIWork
|
||||
{
|
||||
UIType type = UIType::STATE;
|
||||
bool available = false;
|
||||
uint64_t epoch = 0;
|
||||
KVMFRClipboardMessage record = {};
|
||||
};
|
||||
|
||||
struct PendingCancel
|
||||
{
|
||||
bool valid = false;
|
||||
KVMFRClipboardMessage record = {};
|
||||
uint64_t deadline = 0;
|
||||
};
|
||||
|
||||
struct PendingControl
|
||||
{
|
||||
WorkType type = WorkType::STATE;
|
||||
bool available = false;
|
||||
uint64_t epoch = 0;
|
||||
uint32_t reason = 0;
|
||||
};
|
||||
|
||||
static_assert(std::is_nothrow_move_constructible_v<Work>);
|
||||
static_assert(std::is_nothrow_move_assignable_v<Work>);
|
||||
|
||||
struct IncomingTransfer
|
||||
{
|
||||
uint64_t transfer = 0;
|
||||
uint64_t generation = 0;
|
||||
KVMFRClipboardFormat format = KVMFR_CLIPBOARD_FORMAT_NONE;
|
||||
uint64_t nextOffset = 0;
|
||||
uint32_t nextSequence = 0;
|
||||
uint64_t sizeHint = KVMFR_CLIPBOARD_SIZE_UNKNOWN;
|
||||
bool began = false;
|
||||
bool complete = false;
|
||||
uint32_t error = ERROR_SUCCESS;
|
||||
HANDLE event = nullptr;
|
||||
std::shared_ptr<CClipboardSpool> spool;
|
||||
|
||||
~IncomingTransfer();
|
||||
};
|
||||
|
||||
static constexpr UINT WM_CLIPBOARD_WORK = WM_APP + 0x4c;
|
||||
static constexpr size_t MAX_WORK = 16;
|
||||
static constexpr size_t MAX_UI_WORK = 16;
|
||||
static constexpr size_t MAX_PENDING_CANCEL = 8;
|
||||
static constexpr size_t MAX_PENDING_CONTROL = 16;
|
||||
static constexpr DWORD RENDER_TIMEOUT_MS = 15000;
|
||||
static constexpr DWORD SEND_TIMEOUT_MS = RENDER_TIMEOUT_MS;
|
||||
|
||||
HWND m_hwnd;
|
||||
CClipboardChannel& m_channel;
|
||||
HANDLE m_stop = nullptr;
|
||||
HANDLE m_wake = nullptr;
|
||||
HANDLE m_thread = nullptr;
|
||||
|
||||
std::mutex m_workLock;
|
||||
std::array<std::optional<Work>, MAX_WORK> m_recordWork;
|
||||
size_t m_recordWorkCount = 0;
|
||||
std::array<std::optional<Work>, MAX_WORK> m_sendWork;
|
||||
size_t m_sendWorkCount = 0;
|
||||
std::array<PendingCancel, MAX_PENDING_CANCEL> m_pendingCancel;
|
||||
size_t m_pendingCancelCursor = 0;
|
||||
std::array<PendingControl, MAX_PENDING_CONTROL> m_pendingControl;
|
||||
size_t m_pendingControlCount = 0;
|
||||
|
||||
std::recursive_mutex m_uiLock;
|
||||
std::array<UIWork, MAX_UI_WORK> m_uiWork;
|
||||
size_t m_uiWorkCount = 0;
|
||||
|
||||
std::mutex m_transferLock;
|
||||
std::shared_ptr<IncomingTransfer> m_incoming;
|
||||
std::mutex m_outgoingLock;
|
||||
|
||||
UINT m_formatPNG = 0;
|
||||
UINT m_formatJPEG = 0;
|
||||
UINT m_formatOrigin = 0;
|
||||
bool m_listener = false;
|
||||
std::atomic<bool> 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;
|
||||
uint64_t m_remoteControlGeneration = 0;
|
||||
uint64_t m_remoteGeneration = 0;
|
||||
uint32_t m_remoteFormats = 0;
|
||||
DWORD m_ownedSequence = 0;
|
||||
std::atomic<uint64_t> m_liveLocalGeneration { 0 };
|
||||
std::atomic<uint64_t> m_nextTransfer {
|
||||
KVMFR_CLIPBOARD_TRANSFER_HELPER | UINT64_C(1) };
|
||||
std::atomic<uint64_t> m_outgoingTransfer { 0 };
|
||||
KVMFRClipboardMessage m_pendingRemoteOffer = {};
|
||||
uint64_t m_remoteRetryDeadline = 0;
|
||||
|
||||
static DWORD WINAPI ThreadProc(void * context);
|
||||
void Thread();
|
||||
|
||||
bool QueueWork(Work&& work);
|
||||
bool QueueCancel(const KVMFRClipboardMessage& record, uint32_t reason,
|
||||
uint64_t deadline = 0);
|
||||
void QueueControl(WorkType type, bool available,
|
||||
uint64_t epoch, uint32_t reason);
|
||||
bool QueueUI(UIWork&& work);
|
||||
void DrainUI();
|
||||
void ProcessWork(Work&& work);
|
||||
void ProcessRecord(const KVMFRClipboardMessage& record,
|
||||
const uint8_t * data);
|
||||
void ProcessData(const KVMFRClipboardMessage& record,
|
||||
const uint8_t * data);
|
||||
void ProcessSend(Work&& work);
|
||||
void ProcessSendData(Work&& work);
|
||||
void CancelIncoming(uint32_t reason, uint64_t transfer = 0);
|
||||
void ReleaseOutgoing(uint64_t transfer);
|
||||
|
||||
void HandleState(bool available, uint64_t epoch);
|
||||
void HandleOffer(const KVMFRClipboardMessage& record);
|
||||
void HandleClear(const KVMFRClipboardMessage& record);
|
||||
void HandleRequest(const KVMFRClipboardMessage& record);
|
||||
void HandleClipboardUpdate();
|
||||
void HandleDestroyClipboard();
|
||||
void RenderFormat(UINT format, uint64_t deadline = 0);
|
||||
void RenderAllFormats();
|
||||
void RetryRemoteOffer();
|
||||
|
||||
bool OpenClipboardRetry() const;
|
||||
bool IsOurClipboard();
|
||||
uint32_t EnumerateFormats() const;
|
||||
void PublishLocalClipboard();
|
||||
void PublishClear(uint64_t generation);
|
||||
bool ApplyRemoteOffer(uint32_t formats, uint64_t generation);
|
||||
void ClearOwnedClipboard();
|
||||
void InvalidateOutgoing(uint32_t reason);
|
||||
void InvalidateLocalClipboard(uint32_t reason);
|
||||
void ClearRemoteRetry();
|
||||
bool SetOriginMarker(uint64_t generation) const;
|
||||
|
||||
KVMFRClipboardFormat ToWireFormat(UINT format) const;
|
||||
UINT ToWindowsFormat(KVMFRClipboardFormat format) const;
|
||||
std::shared_ptr<CClipboardSpool> CaptureFormat(
|
||||
KVMFRClipboardFormat format, DWORD sequence);
|
||||
bool MaterializeFormat(KVMFRClipboardFormat format,
|
||||
CClipboardSpool& spool);
|
||||
|
||||
void ClipboardState(bool available, uint64_t epoch) override;
|
||||
ClipboardChannelResult ClipboardRecord(const KVMFRClipboardMessage& record,
|
||||
const uint8_t * data) override;
|
||||
void ClipboardReset(uint64_t epoch, uint32_t reason) override;
|
||||
|
||||
public:
|
||||
CClipboardManager(HWND hwnd, CClipboardChannel& channel);
|
||||
~CClipboardManager();
|
||||
|
||||
CClipboardManager(const CClipboardManager&) = delete;
|
||||
CClipboardManager& operator=(const CClipboardManager&) = delete;
|
||||
|
||||
bool Initialize();
|
||||
void Shutdown();
|
||||
bool HandleMessage(UINT message, WPARAM wParam, LPARAM lParam,
|
||||
LRESULT& result);
|
||||
};
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#include "CNotifyWindow.h"
|
||||
#include "CClipboardManager.h"
|
||||
#include "CConfigWindow.h"
|
||||
#include "Resources.h"
|
||||
#include <CDebug.h>
|
||||
@@ -86,6 +87,11 @@ CNotifyWindow::~CNotifyWindow()
|
||||
|
||||
LRESULT CNotifyWindow::handleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
LRESULT clipboardResult;
|
||||
if (m_clipboard && m_clipboard->HandleMessage(
|
||||
uMsg, wParam, lParam, clipboardResult))
|
||||
return clipboardResult;
|
||||
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_NOTIFY_ICON:
|
||||
@@ -168,6 +174,8 @@ LRESULT CNotifyWindow::onClose()
|
||||
|
||||
LRESULT CNotifyWindow::onDestroy()
|
||||
{
|
||||
if (m_clipboard)
|
||||
m_clipboard->Shutdown();
|
||||
KillTimer(m_hwnd, ID_DISPLAY_CHECK_TIMER);
|
||||
Shell_NotifyIcon(NIM_DELETE, &m_iconData);
|
||||
return 0;
|
||||
@@ -310,6 +318,20 @@ void CNotifyWindow::setRecoveryMode(bool active)
|
||||
DEBUG_ERROR_HR(GetLastError(), "Failed to update recovery state");
|
||||
}
|
||||
|
||||
bool CNotifyWindow::initClipboard(CClipboardChannel& channel)
|
||||
{
|
||||
if (m_clipboard)
|
||||
return true;
|
||||
std::unique_ptr<CClipboardManager> clipboard(
|
||||
new (std::nothrow) CClipboardManager(m_hwnd, channel));
|
||||
if (!clipboard)
|
||||
return false;
|
||||
if (!clipboard->Initialize())
|
||||
return false;
|
||||
m_clipboard = std::move(clipboard);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNotifyWindow::handleResolutionRejected(uint32_t width, uint32_t height,
|
||||
uint32_t requiredSizeMiB)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include <optional>
|
||||
|
||||
class CConfigWindow;
|
||||
class CClipboardChannel;
|
||||
class CClipboardManager;
|
||||
|
||||
class CNotifyWindow : public CWindow
|
||||
{
|
||||
@@ -40,6 +42,7 @@ class CNotifyWindow : public CWindow
|
||||
std::atomic_bool closeRequested;
|
||||
bool m_recoveryActive;
|
||||
std::unique_ptr<CConfigWindow> m_config;
|
||||
std::unique_ptr<CClipboardManager> m_clipboard;
|
||||
|
||||
std::function<void()> m_onSettingChange;
|
||||
std::function<bool()> m_onEnsureOnlyDisplay;
|
||||
@@ -80,6 +83,8 @@ public:
|
||||
uint32_t requiredSizeMiB);
|
||||
void setRecoveryMode(bool active);
|
||||
|
||||
bool initClipboard(CClipboardChannel& channel);
|
||||
|
||||
HWND hwndDialog();
|
||||
void close();
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#include "CPipeClient.h"
|
||||
#include "CClipboardRing.h"
|
||||
#include "CDebug.h"
|
||||
#include "CSRWLock.h"
|
||||
#include "CNotifyWindow.h"
|
||||
@@ -399,7 +400,11 @@ bool CPipeClient::Init()
|
||||
|
||||
void CPipeClient::DeInit()
|
||||
{
|
||||
// Stop first so no endpoint callback can race mapping teardown.
|
||||
m_endpoint.Stop();
|
||||
|
||||
CSRWExclusiveLock lock(m_clipboardSetupLock);
|
||||
ResetClipboardSetupLocked();
|
||||
}
|
||||
|
||||
bool CPipeClient::IsLGIddDeviceAttached()
|
||||
@@ -484,6 +489,88 @@ void CPipeClient::OnPipeConnected()
|
||||
|
||||
if (hasStatus)
|
||||
WriteMsg(status);
|
||||
|
||||
if (!m_clipboardEnabled)
|
||||
return;
|
||||
|
||||
CSRWExclusiveLock setupLock(m_clipboardSetupLock);
|
||||
ResetClipboardSetupLocked();
|
||||
|
||||
LARGE_INTEGER size = {};
|
||||
size.QuadPart = sizeof(ClipboardMapping);
|
||||
m_clipboardMapping = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr,
|
||||
PAGE_READWRITE, size.HighPart, size.LowPart, nullptr);
|
||||
if (!m_clipboardMapping)
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(),
|
||||
"Failed to create the clipboard mapping");
|
||||
return;
|
||||
}
|
||||
|
||||
ClipboardMapping * view = static_cast<ClipboardMapping *>(MapViewOfFile(
|
||||
m_clipboardMapping, FILE_MAP_ALL_ACCESS, 0, 0,
|
||||
sizeof(ClipboardMapping)));
|
||||
if (!view)
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(),
|
||||
"Failed to initialize the clipboard mapping");
|
||||
ResetClipboardSetupLocked();
|
||||
return;
|
||||
}
|
||||
|
||||
++m_clipboardEpochCounter;
|
||||
if (!m_clipboardEpochCounter)
|
||||
++m_clipboardEpochCounter;
|
||||
m_clipboardEpoch = m_clipboardEpochCounter;
|
||||
CClipboardRing::Initialize(*view, m_clipboardEpoch);
|
||||
UnmapViewOfFile(view);
|
||||
|
||||
DWORD serverPid = 0;
|
||||
if (!GetNamedPipeServerProcessId(m_endpoint.NativeHandle(), &serverPid))
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(),
|
||||
"Failed to identify the clipboard mapping target");
|
||||
ResetClipboardSetupLocked();
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE target = OpenProcess(PROCESS_DUP_HANDLE, FALSE, serverPid);
|
||||
HANDLE remote = nullptr;
|
||||
if (!target || !DuplicateHandle(GetCurrentProcess(),
|
||||
m_clipboardMapping, target, &remote, 0, FALSE,
|
||||
DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(),
|
||||
"Failed to share the clipboard mapping with the IDD");
|
||||
if (target)
|
||||
CloseHandle(target);
|
||||
ResetClipboardSetupLocked();
|
||||
return;
|
||||
}
|
||||
|
||||
LGPipeMsg setup = {};
|
||||
setup.size = sizeof(setup);
|
||||
setup.type = LGPipeMsg::CLIPBOARD_SETUP;
|
||||
setup.clipboardSetup.handle =
|
||||
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(remote));
|
||||
setup.clipboardSetup.bytes = sizeof(ClipboardMapping);
|
||||
const bool sent = m_endpoint.Send(&setup, sizeof(setup));
|
||||
if (!sent)
|
||||
{
|
||||
DEBUG_WARN("Failed to send clipboard mapping setup");
|
||||
HANDLE reclaimed = nullptr;
|
||||
if (DuplicateHandle(target, remote, GetCurrentProcess(), &reclaimed,
|
||||
0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
|
||||
CloseHandle(reclaimed);
|
||||
ResetClipboardSetupLocked();
|
||||
}
|
||||
CloseHandle(target);
|
||||
}
|
||||
|
||||
void CPipeClient::OnPipeDisconnected()
|
||||
{
|
||||
CSRWExclusiveLock lock(m_clipboardSetupLock);
|
||||
ResetClipboardSetupLocked();
|
||||
}
|
||||
|
||||
void CPipeClient::ReloadSettings()
|
||||
@@ -740,12 +827,85 @@ bool CPipeClient::OnPipeMessage(const void * message, size_t size)
|
||||
HandleSetRecovery(msg);
|
||||
return true;
|
||||
|
||||
case LGPipeMsg::CLIPBOARD_READY:
|
||||
{
|
||||
CSRWExclusiveLock setupLock(m_clipboardSetupLock);
|
||||
|
||||
if (msg.clipboardReady.status != ERROR_SUCCESS ||
|
||||
msg.clipboardReady.epoch != m_clipboardEpoch)
|
||||
{
|
||||
DEBUG_WARN("IDD rejected the clipboard mapping (%u)",
|
||||
msg.clipboardReady.status);
|
||||
ResetClipboardSetupLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
HANDLE mapping = nullptr;
|
||||
if (!m_clipboardMapping ||
|
||||
!DuplicateHandle(GetCurrentProcess(), m_clipboardMapping,
|
||||
GetCurrentProcess(), &mapping, 0, FALSE, DUPLICATE_SAME_ACCESS) ||
|
||||
!m_clipboard.Attach(mapping, m_clipboardEpoch, true, *this))
|
||||
{
|
||||
DEBUG_ERROR("Failed to activate the clipboard mapping");
|
||||
const uint64_t epoch = m_clipboardEpoch;
|
||||
ResetClipboardSetupLocked();
|
||||
setupLock.Unlock();
|
||||
|
||||
// Tell the IDD that its successful mapping is unusable here, so it
|
||||
// does not leave a one-sided channel advertised as available.
|
||||
LGPipeMsg failure = {};
|
||||
failure.size = sizeof(failure);
|
||||
failure.type = LGPipeMsg::CLIPBOARD_READY;
|
||||
failure.clipboardReady.epoch = epoch;
|
||||
failure.clipboardReady.status = ERROR_NOT_READY;
|
||||
m_endpoint.Send(&failure, sizeof(failure));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case LGPipeMsg::CLIPBOARD_KICK:
|
||||
m_clipboard.Kick(msg.clipboardKick.epoch);
|
||||
return true;
|
||||
|
||||
case LGPipeMsg::CLIPBOARD_RESET:
|
||||
m_clipboard.Reset(
|
||||
msg.clipboardReset.epoch, msg.clipboardReset.reason);
|
||||
return true;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unknown message type %d", msg.type);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CPipeClient::ClipboardKick(uint64_t epoch)
|
||||
{
|
||||
LGPipeMsg msg = {};
|
||||
msg.size = sizeof(msg);
|
||||
msg.type = LGPipeMsg::CLIPBOARD_KICK;
|
||||
msg.clipboardKick.epoch = epoch;
|
||||
return m_endpoint.Send(&msg, sizeof(msg));
|
||||
}
|
||||
|
||||
void CPipeClient::ClipboardResetPeer(uint64_t epoch, uint32_t reason)
|
||||
{
|
||||
LGPipeMsg msg = {};
|
||||
msg.size = sizeof(msg);
|
||||
msg.type = LGPipeMsg::CLIPBOARD_RESET;
|
||||
msg.clipboardReset.epoch = epoch;
|
||||
msg.clipboardReset.reason = reason;
|
||||
m_endpoint.Send(&msg, sizeof(msg));
|
||||
}
|
||||
|
||||
void CPipeClient::ResetClipboardSetupLocked()
|
||||
{
|
||||
m_clipboard.Detach();
|
||||
if (m_clipboardMapping)
|
||||
CloseHandle(m_clipboardMapping);
|
||||
m_clipboardMapping = nullptr;
|
||||
m_clipboardEpoch = 0;
|
||||
}
|
||||
|
||||
void CPipeClient::HandleSetCursorPos(const LGPipeMsg& msg)
|
||||
{
|
||||
SetActiveDesktop();
|
||||
|
||||
@@ -24,14 +24,22 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "CPipeEndpoint.h"
|
||||
#include "CClipboardChannel.h"
|
||||
#include "CSRWLock.h"
|
||||
#include "PipeMsg.h"
|
||||
|
||||
class CPipeClient : private IPipeEndpointHandler
|
||||
class CPipeClient : private IPipeEndpointHandler,
|
||||
public IClipboardChannelDoorbell
|
||||
{
|
||||
private:
|
||||
CPipeEndpoint m_endpoint;
|
||||
CSRWLock m_displayLock;
|
||||
CPipeEndpoint m_endpoint;
|
||||
CClipboardChannel m_clipboard;
|
||||
CSRWLock m_clipboardSetupLock;
|
||||
HANDLE m_clipboardMapping = nullptr;
|
||||
uint64_t m_clipboardEpoch = 0;
|
||||
uint64_t m_clipboardEpochCounter = 0;
|
||||
bool m_clipboardEnabled = false;
|
||||
CSRWLock m_displayLock;
|
||||
|
||||
bool m_recoveryActive = false;
|
||||
bool m_hasRecoveryStatus = false;
|
||||
@@ -50,8 +58,10 @@ private:
|
||||
void HandleGPUStatus(const LGPipeMsg& msg);
|
||||
void HandleResolutionRejected(const LGPipeMsg& msg);
|
||||
void HandleSetRecovery(const LGPipeMsg& msg);
|
||||
void ResetClipboardSetupLocked();
|
||||
|
||||
void OnPipeConnected() override;
|
||||
void OnPipeDisconnected() override;
|
||||
bool ShouldReconnect() override;
|
||||
bool OnPipeMessage(const void * message, size_t size) override;
|
||||
|
||||
@@ -64,6 +74,11 @@ public:
|
||||
void DeInit();
|
||||
bool IsRunning() { return m_endpoint.IsRunning(); }
|
||||
|
||||
CClipboardChannel& Clipboard() { return m_clipboard; }
|
||||
void EnableClipboard() { m_clipboardEnabled = true; }
|
||||
bool ClipboardKick(uint64_t epoch) override;
|
||||
void ClipboardResetPeer(uint64_t epoch, uint32_t reason) override;
|
||||
|
||||
void ReloadSettings();
|
||||
bool EnsureOnlyDisplay();
|
||||
};
|
||||
|
||||
@@ -191,6 +191,7 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CButton.cpp" />
|
||||
<ClCompile Include="CCheckbox.cpp" />
|
||||
<ClCompile Include="CClipboardManager.cpp" />
|
||||
<ClCompile Include="CConfigWindow.cpp" />
|
||||
<ClCompile Include="CEditWidget.cpp" />
|
||||
<ClCompile Include="CGroupBox.cpp" />
|
||||
@@ -207,6 +208,7 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CButton.h" />
|
||||
<ClInclude Include="CCheckbox.h" />
|
||||
<ClInclude Include="CClipboardManager.h" />
|
||||
<ClInclude Include="CConfigWindow.h" />
|
||||
<ClInclude Include="CEditWidget.h" />
|
||||
<ClInclude Include="CGroupBox.h" />
|
||||
|
||||
@@ -57,6 +57,9 @@
|
||||
<ClCompile Include="CCheckbox.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CClipboardManager.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CPipeClient.h">
|
||||
@@ -98,6 +101,9 @@
|
||||
<ClInclude Include="CCheckbox.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CClipboardManager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Resources.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -136,6 +136,11 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _
|
||||
|
||||
CNotifyWindow& window = CNotifyWindow::instance();
|
||||
|
||||
if (!window.initClipboard(g_pipe.Clipboard()))
|
||||
DEBUG_ERROR("Failed to initialize clipboard synchronization");
|
||||
else
|
||||
g_pipe.EnableClipboard();
|
||||
|
||||
// the pipe must be initialized after the CNotifyWindow
|
||||
// has been created to avoid a potential race
|
||||
if (!g_pipe.Init())
|
||||
|
||||
Reference in New Issue
Block a user