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