mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-13 07:17:28 +00:00
[idd] clipboard: arbitrate ownership by active client
This commit is contained in:
@@ -60,6 +60,8 @@ namespace
|
||||
CClipboardHub::CClipboardHub(CClipboardChannel& channel) :
|
||||
m_channel(channel)
|
||||
{
|
||||
for (Source& source : m_sources)
|
||||
source.owner = this;
|
||||
m_channel.SetHandler(this);
|
||||
}
|
||||
|
||||
@@ -68,27 +70,107 @@ CClipboardHub::~CClipboardHub()
|
||||
Stop();
|
||||
}
|
||||
|
||||
void CClipboardHub::AdvanceGenerationNL()
|
||||
ClipboardClaimResult CClipboardHub::Source::Claim(
|
||||
const ClipboardSourceId& source, KVMFRClipboardMessage& cached)
|
||||
{
|
||||
Seq::Inc(m_generation);
|
||||
return owner->Claim(*this, source, cached);
|
||||
}
|
||||
|
||||
bool CClipboardHub::MarkFailed(IClipboardSource& source)
|
||||
void CClipboardHub::Source::Release(const ClipboardSourceId& source)
|
||||
{
|
||||
owner->Release(*this, source);
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardHub::Source::SendClipboard(
|
||||
const KVMFRClipboardMessage& record, const uint8_t * data)
|
||||
{
|
||||
return owner->SendClipboard(*this, record, data);
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardHub::Source::SendClipboardBatch(
|
||||
const ClipboardChannelWrite * records, size_t count, size_t& accepted)
|
||||
{
|
||||
return owner->SendClipboardBatch(*this, records, count, accepted);
|
||||
}
|
||||
|
||||
void CClipboardHub::Source::ClipboardReceiveReady()
|
||||
{
|
||||
owner->ClipboardReceiveReady(*this);
|
||||
}
|
||||
|
||||
void CClipboardHub::Source::ClipboardFailed()
|
||||
{
|
||||
owner->ClipboardFailed(*this);
|
||||
}
|
||||
|
||||
uint32_t CClipboardHub::NextGenerationNL()
|
||||
{
|
||||
Seq::Inc(m_generation);
|
||||
return m_generation;
|
||||
}
|
||||
|
||||
bool CClipboardHub::BindingPresentNL(const Source& source) const
|
||||
{
|
||||
return source.active && source.endpoint && source.backend && source.epoch;
|
||||
}
|
||||
|
||||
bool CClipboardHub::BindingValidNL(const Source& source) const
|
||||
{
|
||||
return BindingPresentNL(source) && source.running && !source.reserved &&
|
||||
!source.failed && source.generation;
|
||||
}
|
||||
|
||||
bool CClipboardHub::OwnerIsNL(
|
||||
const Source& source, const ClipboardSourceId& id) const
|
||||
{
|
||||
return m_owner == &source && m_ownerId.client == id.client &&
|
||||
m_ownerId.generation == id.generation;
|
||||
}
|
||||
|
||||
bool CClipboardHub::MarkFailed(Source& source)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_source != &source || (!m_active && !m_reserved))
|
||||
if (!BindingPresentNL(source) && !source.reserved)
|
||||
return false;
|
||||
|
||||
const bool changed = !m_failed;
|
||||
m_failed = true;
|
||||
m_failurePending = true;
|
||||
const bool changed = !source.failed;
|
||||
source.failed = true;
|
||||
source.failurePending = true;
|
||||
return changed;
|
||||
}
|
||||
|
||||
void CClipboardHub::CachePublicationNL(
|
||||
const KVMFRClipboardMessage& record, Source * source)
|
||||
{
|
||||
if (record.type != KVMFR_CLIPBOARD_MESSAGE_OFFER &&
|
||||
record.type != KVMFR_CLIPBOARD_MESSAGE_CLEAR)
|
||||
return;
|
||||
|
||||
m_cachedClipboard = record;
|
||||
m_cachedSource = source;
|
||||
m_cachedValid = true;
|
||||
}
|
||||
|
||||
void CClipboardHub::InvalidateSourceCacheNL(const Source& source)
|
||||
{
|
||||
if (m_cachedSource != &source)
|
||||
return;
|
||||
if (m_cachedValid &&
|
||||
m_cachedClipboard.type == KVMFR_CLIPBOARD_MESSAGE_CLEAR)
|
||||
{
|
||||
m_cachedSource = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
m_cachedClipboard = {};
|
||||
m_cachedSource = nullptr;
|
||||
m_cachedValid = false;
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardHub::HoldOrDiscard(
|
||||
const KVMFRClipboardMessage& record)
|
||||
{
|
||||
// Preserve the pending clipboard publication until a source is rebound.
|
||||
// Preserve the pending clipboard publication until a client claims it.
|
||||
// Transfer-scoped records belong to the source which admitted them and
|
||||
// must never cross a transport generation.
|
||||
if (record.type == KVMFR_CLIPBOARD_MESSAGE_OFFER ||
|
||||
@@ -139,47 +221,58 @@ bool CClipboardHub::Bind(
|
||||
if (!backend || !epoch)
|
||||
return false;
|
||||
|
||||
bool available;
|
||||
uint32_t generation;
|
||||
uint64_t channelEpoch;
|
||||
Source * selected = nullptr;
|
||||
bool available = false;
|
||||
uint32_t generation = 0;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_stopped || m_source || m_active || m_reserved)
|
||||
if (m_stopped)
|
||||
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;
|
||||
for (Source& slot : m_sources)
|
||||
if ((slot.active || slot.failed || slot.reserved) &&
|
||||
slot.backend == backend && slot.epoch == epoch)
|
||||
return false;
|
||||
for (Source& slot : m_sources)
|
||||
if (!slot.active && !slot.failed && !slot.reserved)
|
||||
{
|
||||
slot.backend = backend;
|
||||
slot.epoch = epoch;
|
||||
slot.generation = NextGenerationNL();
|
||||
slot.endpoint = &source;
|
||||
slot.running = false;
|
||||
slot.active = true;
|
||||
slot.reserved = true;
|
||||
slot.failurePending = false;
|
||||
selected = &slot;
|
||||
available = m_available && !m_resetting;
|
||||
generation = slot.generation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!selected)
|
||||
return false;
|
||||
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
const bool started = source.Start(*this);
|
||||
bool attached = false;
|
||||
const bool started = source.Start(*selected);
|
||||
bool attached = false;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (started && m_source == &source && m_backend == backend &&
|
||||
m_epoch == epoch && m_active && m_reserved && !m_failed &&
|
||||
if (started && selected->endpoint == &source &&
|
||||
selected->backend == backend && selected->epoch == epoch &&
|
||||
selected->active && selected->reserved && !selected->failed &&
|
||||
!m_stopped)
|
||||
{
|
||||
m_running = true;
|
||||
m_reserved = false;
|
||||
available = m_available;
|
||||
generation = m_generation;
|
||||
channelEpoch = m_channelEpoch;
|
||||
attached = true;
|
||||
selected->running = true;
|
||||
selected->reserved = false;
|
||||
available = m_available && !m_resetting;
|
||||
generation = selected->generation;
|
||||
attached = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_active = false;
|
||||
m_running = false;
|
||||
selected->active = false;
|
||||
selected->running = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,99 +280,133 @@ bool CClipboardHub::Bind(
|
||||
{
|
||||
source.Stop();
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_source == &source && m_backend == backend && m_epoch == epoch)
|
||||
if (selected->endpoint == &source && selected->backend == backend &&
|
||||
selected->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;
|
||||
selected->backend = 0;
|
||||
selected->epoch = 0;
|
||||
selected->generation = 0;
|
||||
selected->endpoint = nullptr;
|
||||
selected->running = false;
|
||||
selected->active = false;
|
||||
selected->reserved = false;
|
||||
selected->failed = false;
|
||||
selected->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;
|
||||
Source * selected = nullptr;
|
||||
IClipboardSource * source = nullptr;
|
||||
bool stop = false;
|
||||
bool resetChannel = false;
|
||||
uint64_t channelEpoch = 0;
|
||||
{
|
||||
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();
|
||||
for (Source& slot : m_sources)
|
||||
if ((slot.active || slot.failed || slot.reserved) &&
|
||||
slot.backend == backend && slot.epoch == epoch && slot.endpoint)
|
||||
{
|
||||
selected = &slot;
|
||||
source = slot.endpoint;
|
||||
stop = slot.running || slot.reserved;
|
||||
resetChannel = m_owner == &slot;
|
||||
channelEpoch = m_channelEpoch;
|
||||
slot.active = false;
|
||||
slot.running = false;
|
||||
slot.reserved = true;
|
||||
slot.failed = false;
|
||||
slot.failurePending = false;
|
||||
InvalidateSourceCacheNL(slot);
|
||||
if (resetChannel)
|
||||
{
|
||||
m_owner = nullptr;
|
||||
m_ownerId = {};
|
||||
m_resetting = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!selected)
|
||||
return;
|
||||
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
if (stop)
|
||||
source->Stop();
|
||||
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_source == source && m_backend == backend && m_epoch == epoch)
|
||||
if (selected->endpoint == source && selected->backend == backend &&
|
||||
selected->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;
|
||||
selected->backend = 0;
|
||||
selected->epoch = 0;
|
||||
selected->generation = 0;
|
||||
selected->endpoint = nullptr;
|
||||
selected->running = false;
|
||||
selected->active = false;
|
||||
selected->reserved = false;
|
||||
selected->failed = false;
|
||||
selected->failurePending = false;
|
||||
}
|
||||
lock.Unlock();
|
||||
callbackLock.Unlock();
|
||||
lifecycleLock.Unlock();
|
||||
|
||||
if (resetChannel && channelEpoch)
|
||||
m_channel.Reset(channelEpoch, ERROR_DEVICE_NOT_CONNECTED);
|
||||
}
|
||||
|
||||
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;
|
||||
for (Source& slot : m_sources)
|
||||
{
|
||||
if (!slot.failurePending)
|
||||
continue;
|
||||
source = {};
|
||||
source.backend = slot.backend;
|
||||
source.epoch = slot.epoch;
|
||||
slot.failurePending = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CClipboardHub::Stop()
|
||||
{
|
||||
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||
IClipboardSource * source = nullptr;
|
||||
bool stop = false;
|
||||
Source * sources[MAX_SOURCES] = {};
|
||||
unsigned count = 0;
|
||||
{
|
||||
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();
|
||||
m_stopped = true;
|
||||
m_owner = nullptr;
|
||||
m_cachedSource = nullptr;
|
||||
m_ownerId = {};
|
||||
m_cachedClipboard = {};
|
||||
m_cachedValid = false;
|
||||
m_resetting = false;
|
||||
for (Source& source : m_sources)
|
||||
if (source.endpoint)
|
||||
{
|
||||
source.active = false;
|
||||
source.running = false;
|
||||
source.reserved = true;
|
||||
source.failed = false;
|
||||
source.failurePending = false;
|
||||
sources[count++] = &source;
|
||||
}
|
||||
}
|
||||
|
||||
// Clearing the channel handler is a callback-quiescence barrier. It must
|
||||
@@ -288,41 +415,114 @@ void CClipboardHub::Stop()
|
||||
m_channel.ClearHandler(this);
|
||||
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
if (stop)
|
||||
source->Stop();
|
||||
for (unsigned i = count; i > 0; --i)
|
||||
sources[i - 1]->endpoint->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;
|
||||
for (Source& source : m_sources)
|
||||
{
|
||||
source.backend = 0;
|
||||
source.epoch = 0;
|
||||
source.generation = 0;
|
||||
source.endpoint = nullptr;
|
||||
source.running = false;
|
||||
source.active = false;
|
||||
source.reserved = false;
|
||||
source.failed = false;
|
||||
source.failurePending = false;
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardClaimResult CClipboardHub::Claim(Source& source,
|
||||
const ClipboardSourceId& id, KVMFRClipboardMessage& cached)
|
||||
{
|
||||
cached = {};
|
||||
if (!id.client || !id.generation)
|
||||
return ClipboardClaimResult::STALE;
|
||||
|
||||
uint64_t channelEpoch = 0;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!BindingValidNL(source))
|
||||
return ClipboardClaimResult::STALE;
|
||||
if (!m_available || m_resetting || !m_channelEpoch ||
|
||||
!m_channel.Available())
|
||||
return ClipboardClaimResult::UNAVAILABLE;
|
||||
if (m_owner)
|
||||
return OwnerIsNL(source, id) ?
|
||||
ClipboardClaimResult::ACCEPTED : ClipboardClaimResult::BUSY;
|
||||
|
||||
m_owner = &source;
|
||||
m_ownerId = id;
|
||||
// A client-originated OFFER cannot be served by a replacement client.
|
||||
// Its matching cleanup CLEAR is safe to replay, as is any
|
||||
// Helper-originated publication.
|
||||
if (m_cachedValid && (!m_cachedSource ||
|
||||
m_cachedClipboard.type == KVMFR_CLIPBOARD_MESSAGE_CLEAR))
|
||||
{
|
||||
cached = m_cachedClipboard;
|
||||
cached.generation = source.generation;
|
||||
}
|
||||
channelEpoch = m_channelEpoch;
|
||||
}
|
||||
|
||||
m_channel.Kick(channelEpoch);
|
||||
return ClipboardClaimResult::ACCEPTED;
|
||||
}
|
||||
|
||||
void CClipboardHub::Release(
|
||||
Source& source, const ClipboardSourceId& id)
|
||||
{
|
||||
uint64_t channelEpoch = 0;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!OwnerIsNL(source, id))
|
||||
return;
|
||||
m_owner = nullptr;
|
||||
m_ownerId = {};
|
||||
channelEpoch = m_channelEpoch;
|
||||
}
|
||||
|
||||
if (channelEpoch)
|
||||
m_channel.Kick(channelEpoch);
|
||||
}
|
||||
|
||||
void CClipboardHub::ClipboardState(bool available, uint64_t epoch)
|
||||
{
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
IClipboardSource * source = nullptr;
|
||||
uint32_t generation;
|
||||
Source * sources[MAX_SOURCES] = {};
|
||||
uint32_t generations[MAX_SOURCES] = {};
|
||||
unsigned count = 0;
|
||||
{
|
||||
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;
|
||||
const bool changed = m_available != available ||
|
||||
m_channelEpoch != epoch || m_resetting;
|
||||
m_available = available;
|
||||
m_channelEpoch = epoch;
|
||||
m_resetting = false;
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
m_owner = nullptr;
|
||||
m_cachedSource = nullptr;
|
||||
m_ownerId = {};
|
||||
m_cachedClipboard = {};
|
||||
m_cachedValid = false;
|
||||
for (Source& source : m_sources)
|
||||
if (BindingValidNL(source))
|
||||
{
|
||||
source.generation = NextGenerationNL();
|
||||
sources[count] = &source;
|
||||
generations[count] = source.generation;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
if (source)
|
||||
source->ClipboardState(available, generation);
|
||||
for (unsigned i = 0; i < count; ++i)
|
||||
sources[i]->endpoint->ClipboardState(available, generations[i]);
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardHub::ClipboardRecord(
|
||||
@@ -332,24 +532,39 @@ ClipboardChannelResult CClipboardHub::ClipboardRecord(
|
||||
return ClipboardChannelResult::FAILED;
|
||||
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
IClipboardSource * source = nullptr;
|
||||
uint32_t generation;
|
||||
Source * source = nullptr;
|
||||
IClipboardSource * endpoint = nullptr;
|
||||
ClipboardSourceId ownerId = {};
|
||||
uint32_t generation = 0;
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
if (m_stopped || !m_available)
|
||||
return ClipboardChannelResult::ACCEPTED;
|
||||
if (m_failed || !m_active || !m_running || !m_source)
|
||||
source = m_owner;
|
||||
if (!source || !BindingValidNL(*source))
|
||||
return HoldOrDiscard(record);
|
||||
source = m_source;
|
||||
generation = m_generation;
|
||||
endpoint = source->endpoint;
|
||||
ownerId = m_ownerId;
|
||||
generation = source->generation;
|
||||
}
|
||||
|
||||
// Helper records are transport-agnostic. Stamp them only after selecting
|
||||
// the active transport binding.
|
||||
KVMFRClipboardMessage stamped = record;
|
||||
stamped.generation = generation;
|
||||
const ClipboardChannelResult result =
|
||||
source->SendClipboard(stamped,
|
||||
endpoint->SendClipboard(stamped,
|
||||
data.empty() ? nullptr : data.data());
|
||||
if (result != ClipboardChannelResult::FAILED)
|
||||
if (result == ClipboardChannelResult::ACCEPTED)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
CachePublicationNL(stamped, nullptr);
|
||||
const bool publication = stamped.type == KVMFR_CLIPBOARD_MESSAGE_OFFER ||
|
||||
stamped.type == KVMFR_CLIPBOARD_MESSAGE_CLEAR;
|
||||
return publication && m_owner && !OwnerIsNL(*source, ownerId) ?
|
||||
ClipboardChannelResult::BUSY : result;
|
||||
}
|
||||
if (result == ClipboardChannelResult::BUSY)
|
||||
return result;
|
||||
|
||||
if (MarkFailed(*source))
|
||||
@@ -364,59 +579,78 @@ ClipboardChannelResult CClipboardHub::ClipboardRecord(
|
||||
void CClipboardHub::ClipboardReset(uint64_t epoch, uint32_t reason)
|
||||
{
|
||||
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||
IClipboardSource * source = nullptr;
|
||||
uint32_t generation;
|
||||
Source * sources[MAX_SOURCES] = {};
|
||||
uint32_t generations[MAX_SOURCES] = {};
|
||||
unsigned count = 0;
|
||||
{
|
||||
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;
|
||||
m_owner = nullptr;
|
||||
m_cachedSource = nullptr;
|
||||
m_ownerId = {};
|
||||
m_cachedClipboard = {};
|
||||
m_cachedValid = false;
|
||||
m_resetting = true;
|
||||
for (Source& source : m_sources)
|
||||
if (BindingValidNL(source))
|
||||
{
|
||||
source.generation = NextGenerationNL();
|
||||
sources[count] = &source;
|
||||
generations[count] = source.generation;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
if (source)
|
||||
source->ClipboardReset(generation, reason);
|
||||
for (unsigned i = 0; i < count; ++i)
|
||||
sources[i]->endpoint->ClipboardReset(generations[i], reason);
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardHub::SendClipboard(
|
||||
ClipboardChannelResult CClipboardHub::SendClipboard(Source& source,
|
||||
const KVMFRClipboardMessage& record, const uint8_t * data)
|
||||
{
|
||||
const ClipboardChannelWrite write = { record, data };
|
||||
size_t accepted = 0;
|
||||
return SendClipboardBatch(&write, 1, accepted);
|
||||
const ClipboardChannelWrite write = { record, data };
|
||||
size_t accepted = 0;
|
||||
return SendClipboardBatch(source, &write, 1, accepted);
|
||||
}
|
||||
|
||||
ClipboardChannelResult CClipboardHub::SendClipboardBatch(
|
||||
ClipboardChannelResult CClipboardHub::SendClipboardBatch(Source& source,
|
||||
const ClipboardChannelWrite * records, size_t count, size_t& accepted)
|
||||
{
|
||||
accepted = 0;
|
||||
if (!records || !count || count > KVMFR_CLIPBOARD_SLOT_COUNT)
|
||||
return ClipboardChannelResult::FAILED;
|
||||
|
||||
// Keep the shared lock through Send so Unbind cannot advance the binding
|
||||
// Keep the lock through Send so Unbind cannot advance the binding
|
||||
// generation after validation but before the records enter the channel.
|
||||
CSRWSharedLock lock(m_lock);
|
||||
if (m_stopped || !m_available || !m_active || !m_running || m_failed ||
|
||||
!m_source)
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_stopped || !BindingValidNL(source) || m_owner != &source)
|
||||
return ClipboardChannelResult::FAILED;
|
||||
if (!m_available || m_resetting || !m_channel.Available())
|
||||
return ClipboardChannelResult::BUSY;
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
if (!ValidClientDirection(records[i].record) ||
|
||||
records[i].record.generation != m_generation)
|
||||
records[i].record.generation != source.generation)
|
||||
return ClipboardChannelResult::FAILED;
|
||||
|
||||
return m_channel.SendBatch(records, count, accepted);
|
||||
const ClipboardChannelResult result =
|
||||
m_channel.SendBatch(records, count, accepted);
|
||||
for (size_t i = 0; i < accepted; ++i)
|
||||
CachePublicationNL(records[i].record, &source);
|
||||
if (result == ClipboardChannelResult::FAILED &&
|
||||
(m_resetting || !m_channel.Available()))
|
||||
return ClipboardChannelResult::BUSY;
|
||||
return result;
|
||||
}
|
||||
|
||||
void CClipboardHub::ClipboardReceiveReady()
|
||||
void CClipboardHub::ClipboardReceiveReady(Source& source)
|
||||
{
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
if (m_stopped || !m_available || !m_active || m_failed ||
|
||||
!m_running)
|
||||
if (m_stopped || !m_available || !BindingValidNL(source) ||
|
||||
m_owner != &source)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -425,14 +659,9 @@ void CClipboardHub::ClipboardReceiveReady()
|
||||
m_channel.Kick(epoch);
|
||||
}
|
||||
|
||||
void CClipboardHub::ClipboardFailed()
|
||||
void CClipboardHub::ClipboardFailed(Source& source)
|
||||
{
|
||||
IClipboardSource * source;
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
source = m_source;
|
||||
}
|
||||
if (!source || !MarkFailed(*source))
|
||||
if (!MarkFailed(source))
|
||||
return;
|
||||
|
||||
const uint64_t epoch = m_channel.Epoch();
|
||||
|
||||
@@ -25,33 +25,77 @@
|
||||
#include "transport/IClipboardSource.h"
|
||||
#include "transport/ITransport.h"
|
||||
|
||||
class CClipboardHub final : public IClipboardTarget,
|
||||
private IClipboardChannelHandler
|
||||
class CClipboardHub final : private IClipboardChannelHandler
|
||||
{
|
||||
private:
|
||||
CClipboardChannel& m_channel;
|
||||
static constexpr unsigned MAX_SOURCES = TRANSPORT_MAX_INSTANCES;
|
||||
|
||||
struct Source final : public IClipboardTarget
|
||||
{
|
||||
CClipboardHub * owner = nullptr;
|
||||
BackendId backend = 0;
|
||||
uint32_t epoch = 0;
|
||||
uint32_t generation = 0;
|
||||
IClipboardSource * endpoint = nullptr;
|
||||
bool running = false;
|
||||
bool active = false;
|
||||
bool reserved = false;
|
||||
bool failed = false;
|
||||
bool failurePending = false;
|
||||
|
||||
ClipboardClaimResult Claim(const ClipboardSourceId& source,
|
||||
KVMFRClipboardMessage& cached) override;
|
||||
void Release(const ClipboardSourceId& source) override;
|
||||
ClipboardChannelResult SendClipboard(
|
||||
const KVMFRClipboardMessage& record,
|
||||
const uint8_t * data) override;
|
||||
ClipboardChannelResult SendClipboardBatch(
|
||||
const ClipboardChannelWrite * records,
|
||||
size_t count, size_t& accepted) override;
|
||||
void ClipboardReceiveReady() override;
|
||||
void ClipboardFailed() override;
|
||||
};
|
||||
|
||||
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;
|
||||
Source m_sources[MAX_SOURCES];
|
||||
Source * m_owner = nullptr;
|
||||
Source * m_cachedSource = nullptr;
|
||||
ClipboardSourceId m_ownerId;
|
||||
KVMFRClipboardMessage m_cachedClipboard = {};
|
||||
uint64_t m_channelEpoch = 0;
|
||||
uint32_t m_generation = 1;
|
||||
bool m_cachedValid = false;
|
||||
bool m_available = false;
|
||||
bool m_resetting = false;
|
||||
bool m_stopped = false;
|
||||
|
||||
void AdvanceGenerationNL();
|
||||
bool MarkFailed(IClipboardSource& source);
|
||||
uint32_t NextGenerationNL();
|
||||
bool BindingPresentNL(const Source& source) const;
|
||||
bool BindingValidNL(const Source& source) const;
|
||||
bool OwnerIsNL(
|
||||
const Source& source, const ClipboardSourceId& id) const;
|
||||
bool MarkFailed(Source& source);
|
||||
void CachePublicationNL(
|
||||
const KVMFRClipboardMessage& record, Source * source);
|
||||
void InvalidateSourceCacheNL(const Source& source);
|
||||
ClipboardChannelResult HoldOrDiscard(
|
||||
const KVMFRClipboardMessage& record);
|
||||
|
||||
ClipboardClaimResult Claim(Source& source,
|
||||
const ClipboardSourceId& id, KVMFRClipboardMessage& cached);
|
||||
void Release(Source& source, const ClipboardSourceId& id);
|
||||
ClipboardChannelResult SendClipboard(Source& source,
|
||||
const KVMFRClipboardMessage& record, const uint8_t * data);
|
||||
ClipboardChannelResult SendClipboardBatch(Source& source,
|
||||
const ClipboardChannelWrite * records,
|
||||
size_t count, size_t& accepted);
|
||||
void ClipboardReceiveReady(Source& source);
|
||||
void ClipboardFailed(Source& source);
|
||||
|
||||
void ClipboardState(bool available, uint64_t epoch) override;
|
||||
ClipboardChannelResult ClipboardRecord(
|
||||
const KVMFRClipboardMessage& record,
|
||||
@@ -69,13 +113,4 @@ public:
|
||||
void Unbind(BackendId backend, uint32_t epoch);
|
||||
bool TakeFailure(SourceKey& source);
|
||||
void Stop();
|
||||
|
||||
ClipboardChannelResult SendClipboard(
|
||||
const KVMFRClipboardMessage& record,
|
||||
const uint8_t * data) override;
|
||||
ClipboardChannelResult SendClipboardBatch(
|
||||
const ClipboardChannelWrite * records,
|
||||
size_t count, size_t& accepted) override;
|
||||
void ClipboardReceiveReady() override;
|
||||
void ClipboardFailed() override;
|
||||
};
|
||||
|
||||
@@ -25,11 +25,32 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct ClipboardSourceId
|
||||
{
|
||||
uint64_t client = 0;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
enum class ClipboardClaimResult
|
||||
{
|
||||
ACCEPTED,
|
||||
BUSY,
|
||||
UNAVAILABLE,
|
||||
STALE,
|
||||
};
|
||||
|
||||
class IClipboardTarget
|
||||
{
|
||||
public:
|
||||
virtual ~IClipboardTarget() = default;
|
||||
|
||||
// Claim arbitrates clipboard ownership across every transport. The first
|
||||
// active client is accepted; cached receives the latest publication for
|
||||
// replay by the new owner, or an empty record when none is available.
|
||||
virtual ClipboardClaimResult Claim(const ClipboardSourceId& source,
|
||||
KVMFRClipboardMessage& cached) = 0;
|
||||
virtual void Release(const ClipboardSourceId& source) = 0;
|
||||
|
||||
// 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
|
||||
|
||||
@@ -398,9 +398,11 @@ bool CLGMPClipboardTransport::Start(IClipboardTarget& target)
|
||||
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
m_target = ⌖
|
||||
m_failed = false;
|
||||
m_statusDirty = true;
|
||||
m_target = ⌖
|
||||
m_hubOwner = {};
|
||||
m_hubReleasePending = false;
|
||||
m_failed = false;
|
||||
m_statusDirty = true;
|
||||
}
|
||||
m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr);
|
||||
if (!m_thread)
|
||||
@@ -446,11 +448,13 @@ void CLGMPClipboardTransport::Stop()
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
DropPendingTarget();
|
||||
ResetProtocol(false);
|
||||
m_available = false;
|
||||
m_available = false;
|
||||
m_endpointGeneration = 0;
|
||||
m_target = nullptr;
|
||||
m_failed = false;
|
||||
m_statusDirty = true;
|
||||
m_target = nullptr;
|
||||
m_hubOwner = {};
|
||||
m_hubReleasePending = false;
|
||||
m_failed = false;
|
||||
m_statusDirty = true;
|
||||
}
|
||||
|
||||
bool CLGMPClipboardTransport::IsOwner(
|
||||
@@ -462,15 +466,20 @@ bool CLGMPClipboardTransport::IsOwner(
|
||||
|
||||
bool CLGMPClipboardTransport::OwnerSubscribed() const
|
||||
{
|
||||
if (!m_ownerClientID)
|
||||
return ClientSubscribed(m_ownerClientID);
|
||||
}
|
||||
|
||||
bool CLGMPClipboardTransport::ClientSubscribed(uint32_t clientID) const
|
||||
{
|
||||
if (!clientID || !m_queue)
|
||||
return false;
|
||||
|
||||
uint32_t clients[LGMP_MAX_CLIENTS] = {};
|
||||
unsigned count = 0;
|
||||
unsigned count = 0;
|
||||
if (lgmpHostGetClientIDs(m_queue, clients, &count) != LGMP_OK)
|
||||
return false;
|
||||
for (unsigned i = 0; i < count; ++i)
|
||||
if (clients[i] == m_ownerClientID)
|
||||
if (clients[i] == clientID)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -938,28 +947,28 @@ void CLGMPClipboardTransport::FinishOwnerRelease()
|
||||
if (!m_ownerClientID)
|
||||
return;
|
||||
|
||||
const uint32_t clientID = m_ownerClientID;
|
||||
const uint32_t generation = m_ownerGeneration;
|
||||
const char * const reason = m_ownerReleaseReason ?
|
||||
const uint32_t clientID = m_ownerClientID;
|
||||
const uint32_t generation = m_ownerGeneration;
|
||||
const char * const reason = m_ownerReleaseReason ?
|
||||
m_ownerReleaseReason : "unspecified";
|
||||
const Transfer clientToHelper = m_clientToHelper;
|
||||
const Transfer helperToClient = m_helperToClient;
|
||||
const bool clearHelper = m_releaseClearHelper;
|
||||
m_ownerClientID = 0;
|
||||
m_ownerGeneration = 0;
|
||||
m_ownerReleasing = false;
|
||||
m_releaseClearHelper = false;
|
||||
m_ownerReleaseReason = nullptr;
|
||||
m_ownerDeadline = 0;
|
||||
m_ownerDrainDeadline = 0;
|
||||
m_replayPending = false;
|
||||
const Transfer clientToHelper = m_clientToHelper;
|
||||
const Transfer helperToClient = m_helperToClient;
|
||||
const bool clearHelper = m_releaseClearHelper;
|
||||
m_ownerClientID = 0;
|
||||
m_ownerGeneration = 0;
|
||||
m_ownerReleasing = false;
|
||||
m_releaseClearHelper = false;
|
||||
m_ownerReleaseReason = nullptr;
|
||||
m_ownerDeadline = 0;
|
||||
m_ownerDrainDeadline = 0;
|
||||
m_replayPending = false;
|
||||
ClearQueuedStreamTargets();
|
||||
m_discardClientToHelper = 0;
|
||||
m_discardClientToHelper = 0;
|
||||
m_clientToHelper.Clear();
|
||||
m_helperToClient.Clear();
|
||||
if (helperToClient.Active())
|
||||
m_discardHelperToClient = helperToClient.transfer;
|
||||
m_statusDirty = true;
|
||||
m_statusDirty = true;
|
||||
|
||||
if (clearHelper)
|
||||
{
|
||||
@@ -972,8 +981,9 @@ void CLGMPClipboardTransport::FinishOwnerRelease()
|
||||
{
|
||||
m_files.Reset();
|
||||
m_clientClipboardGeneration = 0;
|
||||
m_clientFormats = 0;
|
||||
m_clientFormats = 0;
|
||||
}
|
||||
m_hubReleasePending = m_hubOwner.client != 0;
|
||||
|
||||
DEBUG_INFO("Clipboard owner %u generation %u released (%s)",
|
||||
clientID, generation, reason);
|
||||
@@ -1002,8 +1012,27 @@ bool CLGMPClipboardTransport::RetryOwnerRelease()
|
||||
return !m_failed;
|
||||
}
|
||||
|
||||
void CLGMPClipboardTransport::ReleaseHubOwner(bool force)
|
||||
{
|
||||
if (!m_hubOwner.client)
|
||||
{
|
||||
m_hubReleasePending = false;
|
||||
return;
|
||||
}
|
||||
if (!force && (m_failed || !m_hubReleasePending || m_ownerClientID ||
|
||||
m_ownerReleasing || m_pendingTarget.valid || m_streamTargetCount ||
|
||||
m_internalTargetCount))
|
||||
return;
|
||||
|
||||
if (m_target)
|
||||
m_target->Release(m_hubOwner);
|
||||
m_hubOwner = {};
|
||||
m_hubReleasePending = false;
|
||||
}
|
||||
|
||||
void CLGMPClipboardTransport::ResetProtocol(bool keepClipboard)
|
||||
{
|
||||
DropPendingClaim();
|
||||
DropPendingTarget();
|
||||
if (m_ownerClientID)
|
||||
ReleaseOwner("endpoint reset", false);
|
||||
@@ -1518,6 +1547,104 @@ void CLGMPClipboardTransport::DropPendingTarget()
|
||||
}
|
||||
}
|
||||
|
||||
void CLGMPClipboardTransport::DropPendingClaim()
|
||||
{
|
||||
if (!m_pendingClaim.valid)
|
||||
return;
|
||||
|
||||
m_pendingClaim.Clear();
|
||||
if (m_queue)
|
||||
{
|
||||
const LGMP_STATUS status = lgmpHostAckData(m_queue);
|
||||
if (status != LGMP_OK)
|
||||
Fail("lgmpHostAckData", status);
|
||||
}
|
||||
}
|
||||
|
||||
bool CLGMPClipboardTransport::RetryClaim()
|
||||
{
|
||||
if (!m_pendingClaim.valid)
|
||||
return true;
|
||||
|
||||
const uint32_t clientID = m_pendingClaim.clientID;
|
||||
const KVMFRClipboardMessage message = m_pendingClaim.record;
|
||||
if (!ClientSubscribed(clientID) || !m_available ||
|
||||
!ValidateClaim(message))
|
||||
{
|
||||
DropPendingClaim();
|
||||
return !m_failed;
|
||||
}
|
||||
|
||||
if (m_ownerClientID)
|
||||
{
|
||||
if (!m_ownerReleasing && IsOwner(clientID, message.generation))
|
||||
RenewLease();
|
||||
DropPendingClaim();
|
||||
return !m_failed;
|
||||
}
|
||||
if (m_hubReleasePending)
|
||||
return true;
|
||||
|
||||
const ClipboardSourceId source =
|
||||
{
|
||||
clientID,
|
||||
message.generation,
|
||||
};
|
||||
KVMFRClipboardMessage cached = {};
|
||||
const ClipboardClaimResult claim = m_target ?
|
||||
m_target->Claim(source, cached) : ClipboardClaimResult::UNAVAILABLE;
|
||||
if (claim == ClipboardClaimResult::BUSY ||
|
||||
claim == ClipboardClaimResult::UNAVAILABLE)
|
||||
return true;
|
||||
if (claim != ClipboardClaimResult::ACCEPTED)
|
||||
{
|
||||
DropPendingClaim();
|
||||
return !m_failed;
|
||||
}
|
||||
m_hubOwner = source;
|
||||
|
||||
if (!BindStreams(clientID))
|
||||
{
|
||||
m_target->Release(m_hubOwner);
|
||||
m_hubOwner = {};
|
||||
DeInitStreams();
|
||||
DropPendingClaim();
|
||||
m_failed = true;
|
||||
if (m_stopEvent)
|
||||
SetEvent(m_stopEvent);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ownerClientID = clientID;
|
||||
m_ownerGeneration = message.generation;
|
||||
m_cachedClipboard = {};
|
||||
m_cachedValid = false;
|
||||
m_helperFormats = 0;
|
||||
if (cached.version)
|
||||
{
|
||||
if ((cached.type != KVMFR_CLIPBOARD_MESSAGE_OFFER &&
|
||||
cached.type != KVMFR_CLIPBOARD_MESSAGE_CLEAR) ||
|
||||
!ValidateOutboundRecord(cached))
|
||||
{
|
||||
ReleaseOwner("invalid hub clipboard replay", false, true);
|
||||
DropPendingClaim();
|
||||
m_failed = true;
|
||||
return false;
|
||||
}
|
||||
ApplyOutbound(cached);
|
||||
}
|
||||
RenewLease();
|
||||
m_statusDirty = true;
|
||||
m_replayPending = m_cachedValid;
|
||||
DropPendingClaim();
|
||||
if (m_failed)
|
||||
return false;
|
||||
|
||||
DEBUG_INFO("Clipboard owner %u generation %u acquired",
|
||||
m_ownerClientID, m_ownerGeneration);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CLGMPClipboardTransport::ProcessMessage(
|
||||
uint32_t clientID, const KVMFRClipboardMessage& message)
|
||||
{
|
||||
@@ -1532,24 +1659,14 @@ bool CLGMPClipboardTransport::ProcessMessage(
|
||||
RenewLease();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!BindStreams(clientID))
|
||||
{
|
||||
DeInitStreams();
|
||||
m_failed = true;
|
||||
if (m_stopEvent)
|
||||
SetEvent(m_stopEvent);
|
||||
if (m_pendingClaim.valid)
|
||||
return true;
|
||||
}
|
||||
|
||||
m_ownerClientID = clientID;
|
||||
m_ownerGeneration = message.generation;
|
||||
RenewLease();
|
||||
m_statusDirty = true;
|
||||
m_replayPending = m_cachedValid;
|
||||
DEBUG_INFO("Clipboard owner %u generation %u acquired",
|
||||
m_ownerClientID, m_ownerGeneration);
|
||||
return true;
|
||||
m_pendingClaim.valid = true;
|
||||
m_pendingClaim.clientID = clientID;
|
||||
m_pendingClaim.record = message;
|
||||
(void)RetryClaim();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsOwner(clientID, message.generation))
|
||||
@@ -1625,12 +1742,14 @@ bool CLGMPClipboardTransport::ProcessMessage(
|
||||
|
||||
bool CLGMPClipboardTransport::DrainMessage()
|
||||
{
|
||||
for (unsigned count = 0; count < 64 && !m_pendingTarget.valid; ++count)
|
||||
for (unsigned count = 0; count < 64 && !m_pendingClaim.valid &&
|
||||
!m_pendingTarget.valid && !m_failed; ++count)
|
||||
{
|
||||
uint8_t data[LGMP_MSGS_SIZE] = {};
|
||||
size_t size = 0;
|
||||
uint32_t clientID = 0;
|
||||
const LGMP_STATUS status = lgmpHostReadDataWithSource(
|
||||
|
||||
size_t size = 0;
|
||||
uint32_t clientID = 0;
|
||||
const LGMP_STATUS status = lgmpHostReadDataWithSource(
|
||||
m_queue, data, &size, &clientID);
|
||||
if (status == LGMP_ERR_QUEUE_EMPTY)
|
||||
return true;
|
||||
@@ -1663,7 +1782,7 @@ bool CLGMPClipboardTransport::DrainMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return !m_failed;
|
||||
}
|
||||
|
||||
ClipboardChannelResult CLGMPClipboardTransport::SendControl(
|
||||
@@ -1927,6 +2046,7 @@ void CLGMPClipboardTransport::ClipboardState(
|
||||
|
||||
if (generation != m_endpointGeneration || available != m_available)
|
||||
{
|
||||
DropPendingClaim();
|
||||
DropPendingTarget();
|
||||
ClearStreamTargets();
|
||||
m_internalTargetCount = 0;
|
||||
@@ -1958,6 +2078,7 @@ void CLGMPClipboardTransport::ClipboardReset(
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!generation)
|
||||
return;
|
||||
DropPendingClaim();
|
||||
DropPendingTarget();
|
||||
ClearStreamTargets();
|
||||
m_internalTargetCount = 0;
|
||||
@@ -2009,10 +2130,10 @@ void CLGMPClipboardTransport::Thread()
|
||||
bool notifyFailed = false;
|
||||
for (;;)
|
||||
{
|
||||
uint32_t waitUs = IDLE_POLL_MS * 1000U;
|
||||
bool streamReceived = false;
|
||||
bool notifyReady = false;
|
||||
IClipboardTarget * readyTarget = nullptr;
|
||||
uint32_t waitUs = IDLE_POLL_MS * 1000U;
|
||||
bool streamReceived = false;
|
||||
bool notifyReady = false;
|
||||
IClipboardTarget * readyTarget = nullptr;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_failed)
|
||||
@@ -2033,7 +2154,7 @@ void CLGMPClipboardTransport::Thread()
|
||||
|
||||
// A restarted client can reuse dataset and transfer IDs. Deliver every
|
||||
// old-owner cleanup record before admitting messages from a new owner.
|
||||
if (!RetryTarget() || !RetryStreamTargets() ||
|
||||
if (!RetryClaim() || !RetryTarget() || !RetryStreamTargets() ||
|
||||
!PumpInternalTarget() ||
|
||||
(!m_streamTargetCount && m_internalTargetCount == 0 &&
|
||||
!DrainMessage()) ||
|
||||
@@ -2045,14 +2166,16 @@ void CLGMPClipboardTransport::Thread()
|
||||
notifyFailed = true;
|
||||
break;
|
||||
}
|
||||
ReleaseHubOwner();
|
||||
|
||||
if (m_outboundBlocked)
|
||||
{
|
||||
notifyReady = m_target != nullptr;
|
||||
readyTarget = m_target;
|
||||
}
|
||||
if (m_pendingTarget.valid || m_internalTargetCount ||
|
||||
m_streamTargetCount || m_ownerClientID)
|
||||
if (m_pendingClaim.valid || m_pendingTarget.valid ||
|
||||
m_internalTargetCount ||
|
||||
m_streamTargetCount || m_ownerClientID || m_hubReleasePending)
|
||||
waitUs = ACTIVE_POLL_MS * 1000U;
|
||||
if (m_ownerClientID)
|
||||
{
|
||||
@@ -2115,10 +2238,14 @@ void CLGMPClipboardTransport::Thread()
|
||||
IClipboardTarget * target = nullptr;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
DropPendingClaim();
|
||||
DropPendingTarget();
|
||||
ClearStreamTargets();
|
||||
if (m_ownerClientID)
|
||||
ReleaseOwner("transport stopped", false, true);
|
||||
notifyFailed = notifyFailed || m_failed;
|
||||
if (!notifyFailed)
|
||||
ReleaseHubOwner(true);
|
||||
target = m_target;
|
||||
}
|
||||
if (notifyFailed && target)
|
||||
|
||||
@@ -88,9 +88,9 @@ private:
|
||||
|
||||
struct PendingTarget
|
||||
{
|
||||
bool valid = false;
|
||||
bool acknowledge = false;
|
||||
KVMFRClipboardMessage record = {};
|
||||
bool valid = false;
|
||||
bool acknowledge = false;
|
||||
KVMFRClipboardMessage record = {};
|
||||
|
||||
void Clear()
|
||||
{
|
||||
@@ -100,67 +100,88 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
struct PendingClaim
|
||||
{
|
||||
bool valid = false;
|
||||
uint32_t clientID = 0;
|
||||
KVMFRClipboardMessage record = {};
|
||||
|
||||
void Clear()
|
||||
{
|
||||
valid = false;
|
||||
clientID = 0;
|
||||
record = {};
|
||||
}
|
||||
};
|
||||
|
||||
struct StreamTarget
|
||||
{
|
||||
KVMFRClipboardMessage record = {};
|
||||
uint8_t data[KVMFR_CLIPBOARD_DATA_BYTES] = {};
|
||||
KVMFRClipboardMessage record = {};
|
||||
uint8_t data[KVMFR_CLIPBOARD_DATA_BYTES] = {};
|
||||
|
||||
void Clear() { record = {}; }
|
||||
};
|
||||
|
||||
CLGMPHost& m_host;
|
||||
|
||||
PLGMPHostQueue m_queue = nullptr;
|
||||
PLGMPHostStream m_hostToClientStream = nullptr;
|
||||
PLGMPHostStream m_clientToHostStream = nullptr;
|
||||
PLGMPMemory m_statusMemory [MEMORY_COUNT] = {};
|
||||
PLGMPMemory m_messageMemory[MEMORY_COUNT] = {};
|
||||
PLGMPHostQueue m_queue = nullptr;
|
||||
PLGMPHostStream m_hostToClientStream = nullptr;
|
||||
PLGMPHostStream m_clientToHostStream = nullptr;
|
||||
|
||||
CSRWLock m_lifecycleLock;
|
||||
CSRWLock m_lock;
|
||||
IClipboardTarget * m_target = nullptr;
|
||||
HANDLE m_stopEvent = nullptr;
|
||||
HANDLE m_wakeEvent = nullptr;
|
||||
HANDLE m_pollTimer = nullptr;
|
||||
HANDLE m_thread = nullptr;
|
||||
PLGMPMemory m_statusMemory [MEMORY_COUNT] = {};
|
||||
PLGMPMemory m_messageMemory[MEMORY_COUNT] = {};
|
||||
|
||||
bool m_available = false;
|
||||
bool m_statusDirty = true;
|
||||
bool m_cachedValid = false;
|
||||
bool m_replayPending = false;
|
||||
bool m_outboundBlocked = false;
|
||||
bool m_ownerReleasing = false;
|
||||
bool m_releaseClearHelper = false;
|
||||
bool m_failed = false;
|
||||
uint32_t m_endpointGeneration = 0;
|
||||
uint32_t m_ownerClientID = 0;
|
||||
uint32_t m_ownerGeneration = 0;
|
||||
uint32_t m_statusSerial = 0;
|
||||
uint32_t m_messageSerial = 0;
|
||||
uint32_t m_helperFormats = 0;
|
||||
uint32_t m_clientFormats = 0;
|
||||
uint32_t m_blockedOwnerClientID = 0;
|
||||
uint32_t m_blockedOwnerGeneration = 0;
|
||||
uint64_t m_ownerDeadline = 0;
|
||||
uint64_t m_ownerDrainDeadline = 0;
|
||||
uint64_t m_clientClipboardGeneration = 0;
|
||||
uint64_t m_discardClientToHelper = 0;
|
||||
uint64_t m_discardHelperToClient = 0;
|
||||
const char * m_ownerReleaseReason = nullptr;
|
||||
KVMFRClipboardMessage m_cachedClipboard = {};
|
||||
KVMFRClipboardMessage m_blockedOutbound = {};
|
||||
KVMFRStreamDescriptor m_hostToClientDescriptor = {};
|
||||
KVMFRStreamDescriptor m_clientToHostDescriptor = {};
|
||||
Transfer m_clientToHelper;
|
||||
Transfer m_helperToClient;
|
||||
PendingTarget m_pendingTarget;
|
||||
StreamTarget m_streamTarget[STREAM_TARGET_COUNT];
|
||||
unsigned m_streamTargetHead = 0;
|
||||
unsigned m_streamTargetCount = 0;
|
||||
unsigned m_streamTargetPrepared = 0;
|
||||
KVMFRClipboardMessage m_internalTarget[INTERNAL_TARGET_COUNT] = {};
|
||||
unsigned m_internalTargetCount = 0;
|
||||
CLGMPClipboardFiles m_files;
|
||||
CSRWLock m_lifecycleLock;
|
||||
CSRWLock m_lock;
|
||||
IClipboardTarget * m_target = nullptr;
|
||||
ClipboardSourceId m_hubOwner;
|
||||
HANDLE m_stopEvent = nullptr;
|
||||
HANDLE m_wakeEvent = nullptr;
|
||||
HANDLE m_pollTimer = nullptr;
|
||||
HANDLE m_thread = nullptr;
|
||||
|
||||
bool m_available = false;
|
||||
bool m_statusDirty = true;
|
||||
bool m_cachedValid = false;
|
||||
bool m_replayPending = false;
|
||||
bool m_outboundBlocked = false;
|
||||
bool m_ownerReleasing = false;
|
||||
bool m_releaseClearHelper = false;
|
||||
bool m_hubReleasePending = false;
|
||||
bool m_failed = false;
|
||||
uint32_t m_endpointGeneration = 0;
|
||||
uint32_t m_ownerClientID = 0;
|
||||
uint32_t m_ownerGeneration = 0;
|
||||
uint32_t m_statusSerial = 0;
|
||||
uint32_t m_messageSerial = 0;
|
||||
uint32_t m_helperFormats = 0;
|
||||
uint32_t m_clientFormats = 0;
|
||||
uint32_t m_blockedOwnerClientID = 0;
|
||||
uint32_t m_blockedOwnerGeneration = 0;
|
||||
uint64_t m_ownerDeadline = 0;
|
||||
uint64_t m_ownerDrainDeadline = 0;
|
||||
uint64_t m_clientClipboardGeneration = 0;
|
||||
uint64_t m_discardClientToHelper = 0;
|
||||
uint64_t m_discardHelperToClient = 0;
|
||||
const char * m_ownerReleaseReason = nullptr;
|
||||
KVMFRClipboardMessage m_cachedClipboard = {};
|
||||
KVMFRClipboardMessage m_blockedOutbound = {};
|
||||
KVMFRStreamDescriptor m_hostToClientDescriptor = {};
|
||||
KVMFRStreamDescriptor m_clientToHostDescriptor = {};
|
||||
Transfer m_clientToHelper;
|
||||
Transfer m_helperToClient;
|
||||
PendingClaim m_pendingClaim;
|
||||
PendingTarget m_pendingTarget;
|
||||
|
||||
StreamTarget m_streamTarget[STREAM_TARGET_COUNT];
|
||||
unsigned m_streamTargetHead = 0;
|
||||
unsigned m_streamTargetCount = 0;
|
||||
unsigned m_streamTargetPrepared = 0;
|
||||
|
||||
KVMFRClipboardMessage m_internalTarget[INTERNAL_TARGET_COUNT] = {};
|
||||
unsigned m_internalTargetCount = 0;
|
||||
|
||||
CLGMPClipboardFiles m_files;
|
||||
|
||||
bool Initialize();
|
||||
void DeInit();
|
||||
@@ -175,6 +196,7 @@ private:
|
||||
void Wake() const;
|
||||
|
||||
bool IsOwner(uint32_t clientID, uint32_t generation) const;
|
||||
bool ClientSubscribed(uint32_t clientID) const;
|
||||
bool OwnerSubscribed() const;
|
||||
void RenewLease();
|
||||
void BlockOutbound(const KVMFRClipboardMessage& record,
|
||||
@@ -185,6 +207,7 @@ private:
|
||||
bool force = false);
|
||||
void FinishOwnerRelease();
|
||||
bool RetryOwnerRelease();
|
||||
void ReleaseHubOwner(bool force = false);
|
||||
void ResetProtocol(bool keepClipboard);
|
||||
void QueueHelperClear();
|
||||
void QueueTransferCancel(const Transfer& transfer);
|
||||
@@ -214,6 +237,8 @@ private:
|
||||
bool DrainMessage();
|
||||
bool ProcessMessage(uint32_t clientID,
|
||||
const KVMFRClipboardMessage& message);
|
||||
bool RetryClaim();
|
||||
void DropPendingClaim();
|
||||
bool RetryTarget();
|
||||
void FinishTarget(bool accepted);
|
||||
void DropPendingTarget();
|
||||
|
||||
@@ -58,6 +58,16 @@ namespace
|
||||
return left.client == right.client && left.generation == right.generation;
|
||||
}
|
||||
|
||||
ClipboardSourceId SourceId(
|
||||
const NetworkClipboardPeer& peer, uint64_t claimEpoch)
|
||||
{
|
||||
return
|
||||
{
|
||||
(static_cast<uint64_t>(peer.generation) << 32U) | peer.client,
|
||||
claimEpoch,
|
||||
};
|
||||
}
|
||||
|
||||
uint64_t Next64(uint64_t value)
|
||||
{
|
||||
++value;
|
||||
@@ -269,6 +279,8 @@ struct CNetworkClipboard::Impl
|
||||
|
||||
std::vector<PeerState> m_peers = {};
|
||||
NetworkClipboardPeer m_owner = {};
|
||||
ClipboardSourceId m_hubOwner = {};
|
||||
bool m_hubReleasePending = false;
|
||||
uint64_t m_ownerID = 0;
|
||||
uint64_t m_claimEpoch = 0;
|
||||
uint64_t m_claimSerial = 0;
|
||||
@@ -396,6 +408,7 @@ struct CNetworkClipboard::Impl
|
||||
|
||||
void PumpSends();
|
||||
void PumpTargets();
|
||||
void ReleaseHubOwner(bool force = false);
|
||||
void NotifyTargetReady();
|
||||
void NotifyFailure();
|
||||
void DropOwnerNL(bool clearHelper);
|
||||
@@ -420,9 +433,11 @@ struct CNetworkClipboard::Impl
|
||||
std::vector<SendItem>& batch, const KVMFRClipboardMessage& record);
|
||||
|
||||
NetworkClipboardPacketResult HandleClaimNL(
|
||||
PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs);
|
||||
PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs,
|
||||
CSRWExclusiveLock& stateLock);
|
||||
NetworkClipboardPacketResult HandleClipboardNL(
|
||||
PeerState& peer, const LGNetPacketView& packet, bool bulk, uint64_t nowMs);
|
||||
PeerState& peer, const LGNetPacketView& packet, bool bulk, uint64_t nowMs,
|
||||
CSRWExclusiveLock& stateLock);
|
||||
NetworkClipboardPacketResult HandleFileNL(
|
||||
PeerState& peer, const LGNetPacketView& packet, bool bulk);
|
||||
|
||||
@@ -704,6 +719,7 @@ void CNetworkClipboard::Impl::PumpSends()
|
||||
|
||||
void CNetworkClipboard::Impl::PumpTargets()
|
||||
{
|
||||
ReleaseHubOwner();
|
||||
for (size_t count = 0; count < PROCESS_MAX; ++count)
|
||||
{
|
||||
TargetItem action;
|
||||
@@ -758,22 +774,60 @@ void CNetworkClipboard::Impl::PumpTargets()
|
||||
m_targetBytes -= sizeof(item.record) + item.data.size();
|
||||
m_targets.pop_front();
|
||||
}
|
||||
ReleaseHubOwner();
|
||||
}
|
||||
}
|
||||
|
||||
void CNetworkClipboard::Impl::ReleaseHubOwner(bool force)
|
||||
{
|
||||
CSRWExclusiveLock callbackLock(m_targetCallbackLock);
|
||||
ClipboardSourceId source = {};
|
||||
IClipboardTarget * target = nullptr;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!m_hubReleasePending)
|
||||
return;
|
||||
// A terminal failure retains ownership until CClipboardHub::Unbind resets
|
||||
// the Helper channel, preventing partial transfers reaching a new owner.
|
||||
if (!force && (m_failed || !m_targets.empty()))
|
||||
return;
|
||||
|
||||
source = m_hubOwner;
|
||||
target = m_target;
|
||||
if (!source.client || !source.generation || !target)
|
||||
{
|
||||
if (force)
|
||||
{
|
||||
m_hubOwner = {};
|
||||
m_hubReleasePending = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_targetCallbackActive = true;
|
||||
}
|
||||
|
||||
target->Release(source);
|
||||
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
m_targetCallbackActive = false;
|
||||
if (m_hubReleasePending &&
|
||||
source.client == m_hubOwner.client &&
|
||||
source.generation == m_hubOwner.generation)
|
||||
{
|
||||
m_hubOwner = {};
|
||||
m_hubReleasePending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CNetworkClipboard::Impl::NotifyTargetReady()
|
||||
{
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
if (m_targetCallbackActive)
|
||||
return;
|
||||
}
|
||||
|
||||
CSRWExclusiveLock callbackLock(m_targetCallbackLock);
|
||||
IClipboardTarget * target = nullptr;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_targetCallbackActive || !m_receiveReadyPending)
|
||||
if (!m_receiveReadyPending)
|
||||
return;
|
||||
m_receiveReadyPending = false;
|
||||
target = m_started ? m_target : nullptr;
|
||||
@@ -789,33 +843,17 @@ void CNetworkClipboard::Impl::NotifyTargetReady()
|
||||
|
||||
void CNetworkClipboard::Impl::NotifyFailure()
|
||||
{
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
if (m_targetCallbackActive)
|
||||
return;
|
||||
}
|
||||
|
||||
bool notify = false;
|
||||
CSRWExclusiveLock callbackLock(m_targetCallbackLock);
|
||||
IClipboardTarget * target = nullptr;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!m_targetCallbackActive && m_failed && !m_failureNotified && m_target)
|
||||
{
|
||||
m_failureNotified = true;
|
||||
m_targetCallbackActive = true;
|
||||
notify = true;
|
||||
}
|
||||
if (!m_failed || m_failureNotified || !m_target)
|
||||
return;
|
||||
m_failureNotified = true;
|
||||
m_targetCallbackActive = true;
|
||||
target = m_target;
|
||||
}
|
||||
if (!notify)
|
||||
return;
|
||||
|
||||
CSRWExclusiveLock callbackLock(m_targetCallbackLock);
|
||||
IClipboardTarget * target;
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
target = m_target;
|
||||
}
|
||||
if (target)
|
||||
target->ClipboardFailed();
|
||||
target->ClipboardFailed();
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
m_targetCallbackActive = false;
|
||||
@@ -1101,13 +1139,15 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
|
||||
if (!m_claimEpoch)
|
||||
return;
|
||||
|
||||
bool cleanupQueued = true;
|
||||
if (clearHelper && m_remoteOffer.published && m_remoteOffer.offerID)
|
||||
{
|
||||
TargetItem item;
|
||||
item.record.version = KVMFR_CLIPBOARD_VERSION;
|
||||
item.record.type = KVMFR_CLIPBOARD_MESSAGE_CLEAR;
|
||||
item.record.clipboardGeneration = m_remoteOffer.offerID;
|
||||
QueueTargetNL(std::move(item));
|
||||
if (!QueueTargetNL(std::move(item)))
|
||||
cleanupQueued = false;
|
||||
}
|
||||
|
||||
if (clearHelper)
|
||||
@@ -1120,7 +1160,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
|
||||
item.record.clipboardGeneration = transfer.offerID;
|
||||
item.record.transfer = transfer.localID;
|
||||
item.record.format = transfer.format;
|
||||
QueueTargetNL(std::move(item));
|
||||
if (!QueueTargetNL(std::move(item)))
|
||||
cleanupQueued = false;
|
||||
}
|
||||
|
||||
for (const FileTransfer& transfer : m_fileTransfers)
|
||||
@@ -1132,7 +1173,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
|
||||
item.record.transfer = transfer.localID;
|
||||
item.record.format = KVMFR_CLIPBOARD_FORMAT_FILES;
|
||||
item.record.token = KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED;
|
||||
QueueTargetNL(std::move(item));
|
||||
if (!QueueTargetNL(std::move(item)))
|
||||
cleanupQueued = false;
|
||||
}
|
||||
|
||||
for (const FileAcquisition& acquisition : m_fileAcquisitions)
|
||||
@@ -1158,7 +1200,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
|
||||
item.record.type = KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL;
|
||||
item.record.token = KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED;
|
||||
}
|
||||
QueueTargetNL(std::move(item));
|
||||
if (!QueueTargetNL(std::move(item)))
|
||||
cleanupQueued = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1172,6 +1215,9 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
|
||||
}),
|
||||
m_sends.end());
|
||||
ResetTransfersNL();
|
||||
if (!cleanupQueued)
|
||||
m_failed = true;
|
||||
m_hubReleasePending = m_hubOwner.client != 0;
|
||||
m_remoteOffer.Clear();
|
||||
m_owner = {};
|
||||
m_ownerID = 0;
|
||||
@@ -1184,7 +1230,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
|
||||
}
|
||||
|
||||
NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
|
||||
PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs)
|
||||
PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs,
|
||||
CSRWExclusiveLock& stateLock)
|
||||
{
|
||||
if (!DeliveryFlags(
|
||||
packet.envelope, LG_NET_ENVELOPE_ACK_REQUIRED, false) ||
|
||||
@@ -1225,8 +1272,10 @@ NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
|
||||
NetworkClipboardPacketResult::INVALID;
|
||||
}
|
||||
|
||||
if (m_claimEpoch && !SamePeer(m_owner, peer.peer))
|
||||
if (m_claimEpoch || m_hubReleasePending)
|
||||
{
|
||||
if (m_claimEpoch && SamePeer(m_owner, peer.peer))
|
||||
DropOwnerNL(true);
|
||||
peer.lastClaimEpoch = claim.claimEpoch;
|
||||
return QueueClaimStatusNL(peer.peer, claim.claimEpoch,
|
||||
LG_NET_CLIPBOARD_CLAIM_REJECTED, 0, 0, 2) ?
|
||||
@@ -1234,8 +1283,45 @@ NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
|
||||
NetworkClipboardPacketResult::INVALID;
|
||||
}
|
||||
|
||||
if (m_claimEpoch)
|
||||
DropOwnerNL(true);
|
||||
const ClipboardSourceId source =
|
||||
SourceId(peer.peer, claim.claimEpoch);
|
||||
KVMFRClipboardMessage cached = {};
|
||||
IClipboardTarget * target = m_target;
|
||||
m_targetCallbackActive = target != nullptr;
|
||||
stateLock.Unlock();
|
||||
const ClipboardClaimResult claimResult = target ?
|
||||
target->Claim(source, cached) : ClipboardClaimResult::UNAVAILABLE;
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
m_targetCallbackActive = false;
|
||||
if (claimResult != ClipboardClaimResult::ACCEPTED)
|
||||
{
|
||||
const uint32_t detail = claimResult == ClipboardClaimResult::STALE ? 1 :
|
||||
claimResult == ClipboardClaimResult::BUSY ? 2 : 3;
|
||||
peer.lastClaimEpoch = claim.claimEpoch;
|
||||
return QueueClaimStatusNL(peer.peer, claim.claimEpoch,
|
||||
LG_NET_CLIPBOARD_CLAIM_REJECTED, 0, 0, detail) ?
|
||||
NetworkClipboardPacketResult::HANDLED :
|
||||
NetworkClipboardPacketResult::INVALID;
|
||||
}
|
||||
|
||||
m_hubOwner = source;
|
||||
m_hubReleasePending = false;
|
||||
m_cachedRecord = {};
|
||||
m_cachedValid = false;
|
||||
if (cached.version)
|
||||
{
|
||||
const bool valid = cached.generation == m_generation &&
|
||||
((cached.type == KVMFR_CLIPBOARD_MESSAGE_OFFER && ValidOffer(cached)) ||
|
||||
(cached.type == KVMFR_CLIPBOARD_MESSAGE_CLEAR && ValidClear(cached)));
|
||||
if (!valid)
|
||||
{
|
||||
m_hubReleasePending = true;
|
||||
peer.lastClaimEpoch = claim.claimEpoch;
|
||||
return NetworkClipboardPacketResult::INVALID;
|
||||
}
|
||||
m_cachedRecord = cached;
|
||||
m_cachedValid = true;
|
||||
}
|
||||
|
||||
m_owner = peer.peer;
|
||||
m_ownerID = claim.ownerID;
|
||||
@@ -1264,14 +1350,15 @@ NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
|
||||
}
|
||||
|
||||
NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClipboardNL(
|
||||
PeerState& peer, const LGNetPacketView& packet, bool bulk, uint64_t nowMs)
|
||||
PeerState& peer, const LGNetPacketView& packet, bool bulk, uint64_t nowMs,
|
||||
CSRWExclusiveLock& stateLock)
|
||||
{
|
||||
const LGNetEnvelope& envelope = packet.envelope;
|
||||
if (envelope.messageType == LG_NET_CLIPBOARD_MESSAGE_CLAIM)
|
||||
{
|
||||
if (bulk)
|
||||
return NetworkClipboardPacketResult::INVALID;
|
||||
return HandleClaimNL(peer, packet, nowMs);
|
||||
return HandleClaimNL(peer, packet, nowMs, stateLock);
|
||||
}
|
||||
|
||||
const bool transferData =
|
||||
@@ -3176,15 +3263,21 @@ void CNetworkClipboard::DetachSender()
|
||||
{
|
||||
CSRWExclusiveLock lifecycleLock(m_impl->m_lifecycleLock);
|
||||
CSRWExclusiveLock processLock(m_impl->m_processLock);
|
||||
CSRWExclusiveLock callbackLock(m_impl->m_senderCallbackLock);
|
||||
CSRWExclusiveLock lock(m_impl->m_lock);
|
||||
if (!m_impl->m_sender)
|
||||
return;
|
||||
m_impl->DropOwnerNL(true);
|
||||
m_impl->m_sender = nullptr;
|
||||
m_impl->m_peers.clear();
|
||||
m_impl->m_sends.clear();
|
||||
m_impl->m_sendBytes = 0;
|
||||
{
|
||||
CSRWExclusiveLock callbackLock(m_impl->m_senderCallbackLock);
|
||||
CSRWExclusiveLock lock(m_impl->m_lock);
|
||||
if (!m_impl->m_sender)
|
||||
return;
|
||||
m_impl->DropOwnerNL(true);
|
||||
m_impl->m_sender = nullptr;
|
||||
m_impl->m_peers.clear();
|
||||
m_impl->m_sends.clear();
|
||||
m_impl->m_sendBytes = 0;
|
||||
}
|
||||
m_impl->PumpTargets();
|
||||
processLock.Unlock();
|
||||
m_impl->NotifyTargetReady();
|
||||
m_impl->NotifyFailure();
|
||||
}
|
||||
|
||||
bool CNetworkClipboard::PeerConnected(const NetworkClipboardPeer& peer)
|
||||
@@ -3280,6 +3373,7 @@ NetworkClipboardPacketResult CNetworkClipboard::HandlePacket(
|
||||
CSRWExclusiveLock processLock(m_impl->m_processLock);
|
||||
NetworkClipboardPacketResult result;
|
||||
{
|
||||
CSRWExclusiveLock callbackLock(m_impl->m_targetCallbackLock);
|
||||
CSRWExclusiveLock lock(m_impl->m_lock);
|
||||
Impl::PeerState * state = m_impl->FindPeerNL(peer);
|
||||
if (!state)
|
||||
@@ -3304,7 +3398,8 @@ NetworkClipboardPacketResult CNetworkClipboard::HandlePacket(
|
||||
return NetworkClipboardPacketResult::INVALID;
|
||||
|
||||
result = packet.envelope.service == LG_NET_SERVICE_CLIPBOARD ?
|
||||
m_impl->HandleClipboardNL(*state, packet, bulk, GetTickCount64()) :
|
||||
m_impl->HandleClipboardNL(
|
||||
*state, packet, bulk, GetTickCount64(), lock) :
|
||||
m_impl->HandleFileNL(*state, packet, bulk);
|
||||
}
|
||||
|
||||
@@ -3373,10 +3468,12 @@ bool CNetworkClipboard::Start(IClipboardTarget& target)
|
||||
CSRWExclusiveLock lock(m_impl->m_lock);
|
||||
if (m_impl->m_started || m_impl->m_target)
|
||||
return false;
|
||||
m_impl->m_target = ⌖
|
||||
m_impl->m_started = true;
|
||||
m_impl->m_failed = false;
|
||||
m_impl->m_failureNotified = false;
|
||||
m_impl->m_target = ⌖
|
||||
m_impl->m_started = true;
|
||||
m_impl->m_failed = false;
|
||||
m_impl->m_failureNotified = false;
|
||||
m_impl->m_hubOwner = {};
|
||||
m_impl->m_hubReleasePending = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3385,23 +3482,52 @@ void CNetworkClipboard::Stop()
|
||||
CSRWExclusiveLock lifecycleLock(m_impl->m_lifecycleLock);
|
||||
CSRWExclusiveLock processLock(m_impl->m_processLock);
|
||||
CSRWExclusiveLock callbackLock(m_impl->m_targetCallbackLock);
|
||||
CSRWExclusiveLock lock(m_impl->m_lock);
|
||||
m_impl->m_target = nullptr;
|
||||
m_impl->m_started = false;
|
||||
m_impl->m_stateKnown = false;
|
||||
m_impl->m_available = false;
|
||||
m_impl->m_generation = 0;
|
||||
m_impl->m_failed = false;
|
||||
m_impl->m_failureNotified = false;
|
||||
m_impl->m_targetCallbackActive = false;
|
||||
m_impl->m_targets.clear();
|
||||
m_impl->m_targetBytes = 0;
|
||||
m_impl->ResetTransfersNL();
|
||||
m_impl->m_remoteOffer.published = false;
|
||||
m_impl->m_sourceBlocked = false;
|
||||
m_impl->m_sourceBlockedService = 0;
|
||||
m_impl->m_sourceBlockedTransfer = 0;
|
||||
m_impl->m_receiveReadyPending = false;
|
||||
IClipboardTarget * target = nullptr;
|
||||
ClipboardSourceId source = {};
|
||||
bool release = false;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_impl->m_lock);
|
||||
if (m_impl->m_claimEpoch)
|
||||
m_impl->DropOwnerNL(false);
|
||||
target = m_impl->m_target;
|
||||
source = m_impl->m_hubOwner;
|
||||
release = target && source.client &&
|
||||
source.generation;
|
||||
m_impl->m_targetCallbackActive = release;
|
||||
}
|
||||
|
||||
if (release)
|
||||
target->Release(source);
|
||||
|
||||
{
|
||||
CSRWExclusiveLock lock(m_impl->m_lock);
|
||||
m_impl->m_target = nullptr;
|
||||
m_impl->m_started = false;
|
||||
m_impl->m_stateKnown = false;
|
||||
m_impl->m_available = false;
|
||||
m_impl->m_generation = 0;
|
||||
m_impl->m_failed = false;
|
||||
m_impl->m_failureNotified = false;
|
||||
m_impl->m_targetCallbackActive = false;
|
||||
m_impl->m_hubOwner = {};
|
||||
m_impl->m_hubReleasePending = false;
|
||||
m_impl->m_owner = {};
|
||||
m_impl->m_ownerID = 0;
|
||||
m_impl->m_claimEpoch = 0;
|
||||
m_impl->m_claimSerial = 0;
|
||||
m_impl->m_remoteClaimSerial = 0;
|
||||
m_impl->m_remoteOfferSerial = 0;
|
||||
m_impl->m_claimDeadline = 0;
|
||||
m_impl->m_claimLeaseMs = 0;
|
||||
m_impl->m_targetBytes = 0;
|
||||
m_impl->m_remoteOffer.published = false;
|
||||
m_impl->m_sourceBlocked = false;
|
||||
m_impl->m_sourceBlockedService = 0;
|
||||
m_impl->m_sourceBlockedTransfer = 0;
|
||||
m_impl->m_receiveReadyPending = false;
|
||||
m_impl->m_targets.clear();
|
||||
m_impl->ResetTransfersNL();
|
||||
}
|
||||
}
|
||||
|
||||
void CNetworkClipboard::ClipboardState(bool available, uint32_t generation)
|
||||
|
||||
Reference in New Issue
Block a user