mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 23:21:29 +00:00
424 lines
12 KiB
C++
424 lines
12 KiB
C++
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "transport/CClipboardHub.h"
|
|
|
|
#include "Seq.h"
|
|
|
|
namespace
|
|
{
|
|
bool ValidHelperDirection(const KVMFRClipboardMessage& record)
|
|
{
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_REQUEST)
|
|
return kvmfrClipboardTransferFromHelper(record.transfer);
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_DATA)
|
|
return kvmfrClipboardTransferFromClient(record.transfer);
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE ||
|
|
record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_RELEASE ||
|
|
record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST)
|
|
return kvmfrClipboardTransferFromHelper(record.transfer);
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED ||
|
|
record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA)
|
|
return kvmfrClipboardTransferFromClient(record.transfer);
|
|
return true;
|
|
}
|
|
|
|
bool ValidClientDirection(const KVMFRClipboardMessage& record)
|
|
{
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_REQUEST)
|
|
return kvmfrClipboardTransferFromClient(record.transfer);
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_DATA)
|
|
return kvmfrClipboardTransferFromHelper(record.transfer);
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE ||
|
|
record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_RELEASE ||
|
|
record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST)
|
|
return kvmfrClipboardTransferFromClient(record.transfer);
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED ||
|
|
record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA)
|
|
return kvmfrClipboardTransferFromHelper(record.transfer);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
CClipboardHub::CClipboardHub(CClipboardChannel& channel) :
|
|
m_channel(channel)
|
|
{
|
|
m_channel.SetHandler(this);
|
|
}
|
|
|
|
CClipboardHub::~CClipboardHub()
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
void CClipboardHub::AdvanceGenerationNL()
|
|
{
|
|
Seq::Inc(m_generation);
|
|
}
|
|
|
|
bool CClipboardHub::MarkFailed(IClipboardSource& source)
|
|
{
|
|
CSRWExclusiveLock lock(m_lock);
|
|
if (m_source != &source || (!m_active && !m_reserved))
|
|
return false;
|
|
|
|
const bool changed = !m_failed;
|
|
m_failed = true;
|
|
m_failurePending = true;
|
|
return changed;
|
|
}
|
|
|
|
ClipboardChannelResult CClipboardHub::HoldOrDiscard(
|
|
const KVMFRClipboardMessage& record)
|
|
{
|
|
// Preserve the pending clipboard publication until a source is rebound.
|
|
// Transfer-scoped records belong to the source which admitted them and
|
|
// must never cross a transport generation.
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_OFFER ||
|
|
record.type == KVMFR_CLIPBOARD_MESSAGE_CLEAR)
|
|
return ClipboardChannelResult::BUSY;
|
|
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_REQUEST)
|
|
{
|
|
KVMFRClipboardMessage cancel = {};
|
|
cancel.version = KVMFR_CLIPBOARD_VERSION;
|
|
cancel.type = KVMFR_CLIPBOARD_MESSAGE_CANCEL;
|
|
cancel.generation = record.generation;
|
|
cancel.clipboardGeneration = record.clipboardGeneration;
|
|
cancel.transfer = record.transfer;
|
|
cancel.format = record.format;
|
|
cancel.token = ERROR_DEVICE_NOT_CONNECTED;
|
|
const ClipboardChannelResult result = m_channel.Send(cancel);
|
|
if (result != ClipboardChannelResult::ACCEPTED)
|
|
return result;
|
|
}
|
|
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE ||
|
|
record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST)
|
|
{
|
|
KVMFRClipboardMessage reject = {};
|
|
reject.version = KVMFR_CLIPBOARD_VERSION;
|
|
reject.type = record.type ==
|
|
KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE ?
|
|
KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED :
|
|
KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL;
|
|
reject.generation = record.generation;
|
|
reject.clipboardGeneration = record.clipboardGeneration;
|
|
reject.transfer = record.transfer;
|
|
reject.format = KVMFR_CLIPBOARD_FORMAT_FILES;
|
|
reject.token = KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED;
|
|
const ClipboardChannelResult result = m_channel.Send(reject);
|
|
if (result != ClipboardChannelResult::ACCEPTED)
|
|
return result;
|
|
}
|
|
|
|
return ClipboardChannelResult::ACCEPTED;
|
|
}
|
|
|
|
bool CClipboardHub::Bind(
|
|
BackendId backend, uint32_t epoch, IClipboardSource& source)
|
|
{
|
|
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
|
if (!backend || !epoch)
|
|
return false;
|
|
|
|
bool available;
|
|
uint32_t generation;
|
|
uint64_t channelEpoch;
|
|
{
|
|
CSRWExclusiveLock lock(m_lock);
|
|
if (m_stopped || m_source || m_active || m_reserved)
|
|
return false;
|
|
|
|
AdvanceGenerationNL();
|
|
m_source = &source;
|
|
m_backend = backend;
|
|
m_epoch = epoch;
|
|
m_active = true;
|
|
m_reserved = true;
|
|
m_failed = false;
|
|
m_failurePending = false;
|
|
available = m_available;
|
|
generation = m_generation;
|
|
channelEpoch = m_channelEpoch;
|
|
}
|
|
|
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
|
const bool started = source.Start(*this);
|
|
bool attached = false;
|
|
{
|
|
CSRWExclusiveLock lock(m_lock);
|
|
if (started && m_source == &source && m_backend == backend &&
|
|
m_epoch == epoch && m_active && m_reserved && !m_failed &&
|
|
!m_stopped)
|
|
{
|
|
m_running = true;
|
|
m_reserved = false;
|
|
available = m_available;
|
|
generation = m_generation;
|
|
channelEpoch = m_channelEpoch;
|
|
attached = true;
|
|
}
|
|
else
|
|
{
|
|
m_active = false;
|
|
m_running = false;
|
|
}
|
|
}
|
|
|
|
if (!attached)
|
|
{
|
|
source.Stop();
|
|
CSRWExclusiveLock lock(m_lock);
|
|
if (m_source == &source && m_backend == backend && m_epoch == epoch)
|
|
{
|
|
m_source = nullptr;
|
|
m_backend = 0;
|
|
m_epoch = 0;
|
|
m_active = false;
|
|
m_running = false;
|
|
m_reserved = false;
|
|
m_failed = false;
|
|
m_failurePending = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
source.ClipboardState(available, generation);
|
|
callbackLock.Unlock();
|
|
if (available && channelEpoch)
|
|
m_channel.Kick(channelEpoch);
|
|
return true;
|
|
}
|
|
|
|
void CClipboardHub::Unbind(BackendId backend, uint32_t epoch)
|
|
{
|
|
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
|
IClipboardSource * source = nullptr;
|
|
bool stop = false;
|
|
{
|
|
CSRWExclusiveLock lock(m_lock);
|
|
if ((!m_active && !m_failed && !m_reserved) ||
|
|
m_backend != backend || m_epoch != epoch || !m_source)
|
|
return;
|
|
|
|
source = m_source;
|
|
stop = m_running || m_reserved;
|
|
m_active = false;
|
|
m_running = false;
|
|
m_reserved = true;
|
|
m_failed = false;
|
|
m_failurePending = false;
|
|
AdvanceGenerationNL();
|
|
}
|
|
|
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
|
if (stop)
|
|
source->Stop();
|
|
|
|
CSRWExclusiveLock lock(m_lock);
|
|
if (m_source == source && m_backend == backend && m_epoch == epoch)
|
|
{
|
|
m_source = nullptr;
|
|
m_backend = 0;
|
|
m_epoch = 0;
|
|
m_active = false;
|
|
m_running = false;
|
|
m_reserved = false;
|
|
m_failed = false;
|
|
m_failurePending = false;
|
|
}
|
|
}
|
|
|
|
bool CClipboardHub::TakeFailure(SourceKey& source)
|
|
{
|
|
CSRWSharedLock lifecycleLock(m_lifecycleLock);
|
|
CSRWExclusiveLock lock(m_lock);
|
|
if (!m_failurePending)
|
|
return false;
|
|
|
|
source = {};
|
|
source.backend = m_backend;
|
|
source.epoch = m_epoch;
|
|
m_failurePending = false;
|
|
return true;
|
|
}
|
|
|
|
void CClipboardHub::Stop()
|
|
{
|
|
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
|
IClipboardSource * source = nullptr;
|
|
bool stop = false;
|
|
{
|
|
CSRWExclusiveLock lock(m_lock);
|
|
if (m_stopped)
|
|
return;
|
|
|
|
m_stopped = true;
|
|
source = m_source;
|
|
stop = source && (m_running || m_reserved);
|
|
m_active = false;
|
|
m_running = false;
|
|
m_reserved = source != nullptr;
|
|
m_failed = false;
|
|
m_failurePending = false;
|
|
AdvanceGenerationNL();
|
|
}
|
|
|
|
// Clearing the channel handler is a callback-quiescence barrier. It must
|
|
// precede taking m_callbackLock because an in-flight channel callback owns
|
|
// the channel's handler lock while waiting for m_callbackLock.
|
|
m_channel.ClearHandler(this);
|
|
|
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
|
if (stop)
|
|
source->Stop();
|
|
|
|
CSRWExclusiveLock lock(m_lock);
|
|
m_source = nullptr;
|
|
m_backend = 0;
|
|
m_epoch = 0;
|
|
m_active = false;
|
|
m_running = false;
|
|
m_reserved = false;
|
|
m_failed = false;
|
|
m_failurePending = false;
|
|
}
|
|
|
|
void CClipboardHub::ClipboardState(bool available, uint64_t epoch)
|
|
{
|
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
|
IClipboardSource * source = nullptr;
|
|
uint32_t generation;
|
|
{
|
|
CSRWExclusiveLock lock(m_lock);
|
|
if (m_stopped)
|
|
return;
|
|
|
|
if (m_available != available || m_channelEpoch != epoch)
|
|
AdvanceGenerationNL();
|
|
m_available = available;
|
|
m_channelEpoch = epoch;
|
|
generation = m_generation;
|
|
if (m_active && m_running && !m_failed)
|
|
source = m_source;
|
|
}
|
|
|
|
if (source)
|
|
source->ClipboardState(available, generation);
|
|
}
|
|
|
|
ClipboardChannelResult CClipboardHub::ClipboardRecord(
|
|
const KVMFRClipboardMessage& record, const uint8_t * data)
|
|
{
|
|
if (!ValidHelperDirection(record))
|
|
return ClipboardChannelResult::FAILED;
|
|
|
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
|
IClipboardSource * source = nullptr;
|
|
uint32_t generation;
|
|
{
|
|
CSRWSharedLock lock(m_lock);
|
|
if (m_stopped || !m_available)
|
|
return ClipboardChannelResult::ACCEPTED;
|
|
if (m_failed || !m_active || !m_running || !m_source)
|
|
return HoldOrDiscard(record);
|
|
source = m_source;
|
|
generation = m_generation;
|
|
}
|
|
|
|
KVMFRClipboardMessage stamped = record;
|
|
stamped.generation = generation;
|
|
const ClipboardChannelResult result =
|
|
source->SendClipboard(stamped, data);
|
|
if (result != ClipboardChannelResult::FAILED)
|
|
return result;
|
|
|
|
if (MarkFailed(*source))
|
|
{
|
|
const uint64_t epoch = m_channel.Epoch();
|
|
if (epoch)
|
|
m_channel.Kick(epoch);
|
|
}
|
|
return HoldOrDiscard(record);
|
|
}
|
|
|
|
void CClipboardHub::ClipboardReset(uint64_t epoch, uint32_t reason)
|
|
{
|
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
|
IClipboardSource * source = nullptr;
|
|
uint32_t generation;
|
|
{
|
|
CSRWExclusiveLock lock(m_lock);
|
|
if (m_stopped || m_channelEpoch != epoch)
|
|
return;
|
|
|
|
AdvanceGenerationNL();
|
|
generation = m_generation;
|
|
if (m_active && m_running && !m_failed)
|
|
source = m_source;
|
|
}
|
|
|
|
if (source)
|
|
source->ClipboardReset(generation, reason);
|
|
}
|
|
|
|
ClipboardChannelResult CClipboardHub::SendClipboard(
|
|
const KVMFRClipboardMessage& record, const uint8_t * data)
|
|
{
|
|
// Keep the shared lock through Send so Unbind cannot advance the binding
|
|
// generation after validation but before the record enters the channel.
|
|
CSRWSharedLock lock(m_lock);
|
|
if (!ValidClientDirection(record) ||
|
|
m_stopped || !m_available || !m_active || !m_running || m_failed ||
|
|
!m_source || record.generation != m_generation)
|
|
return ClipboardChannelResult::FAILED;
|
|
return m_channel.Send(record, data);
|
|
}
|
|
|
|
void CClipboardHub::ClipboardReceiveReady()
|
|
{
|
|
{
|
|
CSRWSharedLock lock(m_lock);
|
|
if (m_stopped || !m_available || !m_active || m_failed ||
|
|
!m_running)
|
|
return;
|
|
}
|
|
|
|
const uint64_t epoch = m_channel.Epoch();
|
|
if (epoch)
|
|
m_channel.Kick(epoch);
|
|
}
|
|
|
|
void CClipboardHub::ClipboardFailed()
|
|
{
|
|
IClipboardSource * source;
|
|
{
|
|
CSRWSharedLock lock(m_lock);
|
|
source = m_source;
|
|
}
|
|
if (!source || !MarkFailed(*source))
|
|
return;
|
|
|
|
const uint64_t epoch = m_channel.Epoch();
|
|
if (epoch)
|
|
m_channel.Kick(epoch);
|
|
}
|