Files
LookingGlass/idd/LGIdd/transport/CInputHub.cpp
2026-08-13 21:44:07 +10:00

726 lines
19 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/CInputHub.h"
#include "input/IInputSink.h"
#include "Seq.h"
static bool SameClient(const SourceKey& left, const SourceKey& right)
{
return
left.backend == right.backend &&
left.epoch == right.epoch &&
left.client == right.client;
}
CInputHub::CInputHub()
{
for (Source& source : m_sources)
source.owner = this;
}
CInputHub::~CInputHub()
{
Stop();
for (Source& source : m_sources)
{
BackendId backend;
uint32_t epoch;
{
CSRWSharedLock lock(m_lock);
backend = source.backend;
epoch = source.epoch;
}
if (backend && epoch)
Unbind(backend, epoch);
}
}
SourceKey CInputHub::Source::Key(const InputSourceId& sourceId) const
{
SourceKey source;
source.backend = backend;
source.epoch = epoch;
source.client = sourceId.client;
source.generation = sourceId.generation;
return source;
}
InputTargetState CInputHub::Source::GetState(
const InputSourceId& source)
{
return owner->GetState(Key(source));
}
void CInputHub::Source::Failed()
{
owner->Failed(*this);
}
InputResult CInputHub::Source::Claim(const InputSourceId& source)
{
return owner->Claim(Key(source));
}
InputResult CInputHub::Source::Touch(const InputSourceId& source)
{
return owner->Touch(Key(source));
}
InputResult CInputHub::Source::Release(
const InputSourceId& source, bool reset)
{
return owner->Release(Key(source), reset);
}
InputResult CInputHub::Source::SendMouseRelative(
const InputSourceId& source,
int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons)
{
return owner->SendMouseRelative(
Key(source), deltaX, deltaY, wheel, buttons);
}
InputResult CInputHub::Source::SendMouseAbsolute(
const InputSourceId& source,
uint16_t x, uint16_t y, int32_t wheel, uint32_t buttons)
{
return owner->SendMouseAbsolute(Key(source), x, y, wheel, buttons);
}
InputResult CInputHub::Source::SendKeyboard(const InputSourceId& source,
uint8_t modifiers, const uint8_t * keys)
{
return owner->SendKeyboard(Key(source), modifiers, keys);
}
InputResult CInputHub::Source::Reset(const InputSourceId& source)
{
return owner->Reset(Key(source));
}
bool CInputHub::Bind(
BackendId backend, uint32_t epoch, IInputSource& input)
{
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
if (!backend || !epoch)
return false;
Source * selected = nullptr;
bool start = false;
{
CSRWExclusiveLock lock(m_lock);
for (Source& source : m_sources)
if ((source.active || source.failed || source.reserved) &&
source.backend == backend && source.epoch == epoch)
return false;
for (Source& source : m_sources)
if (!source.active && !source.failed && !source.reserved)
{
source.backend = backend;
source.epoch = epoch;
source.endpoint = &input;
source.active = true;
source.reserved = true;
selected = &source;
start = m_started;
break;
}
}
if (!selected)
return false;
if (start && !input.Start(*selected))
{
input.Stop();
CSRWExclusiveLock lock(m_lock);
if (selected->reserved && selected->endpoint == &input &&
selected->backend == backend && selected->epoch == epoch)
{
selected->backend = 0;
selected->epoch = 0;
selected->endpoint = nullptr;
selected->running = false;
selected->active = false;
selected->failed = false;
selected->failurePending = false;
selected->reserved = false;
}
return false;
}
{
CSRWExclusiveLock lock(m_lock);
if (!selected->reserved || selected->endpoint != &input ||
selected->backend != backend || selected->epoch != epoch ||
selected->failed)
{
lock.Unlock();
if (start)
input.Stop();
{
CSRWExclusiveLock clearLock(m_lock);
if (selected->endpoint == &input &&
selected->backend == backend && selected->epoch == epoch)
{
selected->backend = 0;
selected->epoch = 0;
selected->endpoint = nullptr;
selected->running = false;
selected->active = false;
selected->failed = false;
selected->failurePending = false;
selected->reserved = false;
}
}
return false;
}
selected->running = start;
selected->reserved = false;
}
return true;
}
void CInputHub::Unbind(BackendId backend, uint32_t epoch)
{
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
Source * selected = nullptr;
IInputSource * input = nullptr;
bool stop = false;
{
CSRWExclusiveLock lock(m_lock);
for (Source& source : m_sources)
if ((source.active || source.failed || source.reserved) &&
source.backend == backend && source.epoch == epoch)
{
source.active = false;
source.failed = false;
source.failurePending = false;
source.reserved = true;
input = source.endpoint;
stop = source.running;
source.running = false;
selected = &source;
if (m_owner.backend == backend && m_owner.epoch == epoch)
{
if (m_sink)
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
}
if (m_interactionOwner.backend == backend &&
m_interactionOwner.epoch == epoch)
{
ClearInteraction();
}
AdvanceInteractionSerial();
break;
}
}
if (!selected)
return;
if (input && stop)
input->Stop();
CSRWExclusiveLock lock(m_lock);
if (selected->reserved && selected->backend == backend &&
selected->epoch == epoch)
{
selected->backend = 0;
selected->epoch = 0;
selected->endpoint = nullptr;
selected->running = false;
selected->reserved = false;
selected->failed = false;
selected->failurePending = false;
}
}
InteractionResult CInputHub::CheckInteraction(
SourceKey& source, InteractionPermit& permit)
{
permit = {};
if (!source.backend || !source.epoch || !source.client)
return InteractionResult::STALE;
CSRWExclusiveLock lock(m_lock);
if (m_started && m_sink)
CheckState();
if (m_owner.backend)
{
if (!SameClient(m_owner, source))
return InteractionResult::BUSY;
if (source.generation && source.generation != m_owner.generation)
return InteractionResult::BUSY;
source.generation = m_owner.generation;
permit.serial = m_interactionSerial;
permit.source = source;
return InteractionResult::ACCEPTED;
}
const uint64_t now = GetTickCount64();
if (m_interactionOwner.backend && now >= m_interactionDeadline)
InvalidateInteraction();
if (!m_interactionOwner.backend)
{
permit.serial = m_interactionSerial;
permit.source = source;
return InteractionResult::ACCEPTED;
}
if (!SameClient(m_interactionOwner, source) ||
(source.generation && m_interactionOwner.generation &&
source.generation != m_interactionOwner.generation))
return InteractionResult::BUSY;
if (!source.generation)
source.generation = m_interactionOwner.generation;
permit.serial = m_interactionSerial;
permit.source = source;
return InteractionResult::ACCEPTED;
}
void CInputHub::CommitInteraction(
const SourceKey& source, const InteractionPermit& permit)
{
if (!source.backend || !source.epoch || !source.client || !permit.serial ||
source != permit.source)
return;
CSRWExclusiveLock lock(m_lock);
if (m_started && m_sink)
CheckState();
if (permit.serial != m_interactionSerial)
return;
if (m_owner.backend)
return;
const uint64_t now = GetTickCount64();
if (m_interactionOwner.backend &&
(!SameClient(m_interactionOwner, source) ||
(source.generation && m_interactionOwner.generation &&
source.generation != m_interactionOwner.generation)))
return;
if (!m_interactionOwner.backend)
m_interactionOwner = source;
else if (!m_interactionOwner.generation)
m_interactionOwner.generation = source.generation;
m_interactionDeadline = now + INTERACTION_LEASE_MS;
AdvanceInteractionSerial();
}
void CInputHub::RevokeInteraction(BackendId backend, uint32_t epoch)
{
if (!backend || !epoch)
return;
CSRWExclusiveLock lock(m_lock);
if (m_interactionOwner.backend == backend &&
m_interactionOwner.epoch == epoch)
ClearInteraction();
AdvanceInteractionSerial();
}
bool CInputHub::TakeFailure(SourceKey& source)
{
CSRWSharedLock lifecycleLock(m_lifecycleLock);
CSRWExclusiveLock lock(m_lock);
for (Source& slot : m_sources)
{
if (!slot.failurePending)
continue;
source.backend = slot.backend;
source.epoch = slot.epoch;
slot.failurePending = false;
return true;
}
return false;
}
bool CInputHub::Start(IInputSink& sink)
{
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
Source * sources[MAX_SOURCES] = {};
unsigned count = 0;
{
CSRWExclusiveLock lock(m_lock);
if (m_started)
return m_sink == &sink;
m_sink = &sink;
m_sinkState = sink.GetState();
m_started = true;
InvalidateInteraction();
for (Source& source : m_sources)
if (source.active)
{
source.reserved = true;
sources[count++] = &source;
}
}
for (unsigned i = 0; i < count; ++i)
{
Source& source = *sources[i];
const bool started = source.endpoint->Start(source);
bool stop = !started;
{
CSRWExclusiveLock lock(m_lock);
if (!started || source.failed || !source.active || !source.reserved)
{
if (m_owner.backend == source.backend &&
m_owner.epoch == source.epoch)
{
if (m_sink)
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
}
if (m_interactionOwner.backend == source.backend &&
m_interactionOwner.epoch == source.epoch)
{
ClearInteraction();
}
AdvanceInteractionSerial();
source.active = false;
source.failed = true;
source.failurePending = true;
stop = true;
}
else
source.reserved = false;
source.running = started && !stop;
}
if (!stop)
continue;
source.endpoint->Stop();
{
CSRWExclusiveLock lock(m_lock);
source.running = false;
}
}
return true;
}
void CInputHub::Stop()
{
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
Source * sources[MAX_SOURCES] = {};
unsigned count = 0;
IInputSink * sink = nullptr;
bool reset = false;
{
CSRWExclusiveLock lock(m_lock);
if (!m_started)
{
InvalidateInteraction();
return;
}
m_started = false;
for (Source& source : m_sources)
if (source.endpoint && source.running)
{
source.running = false;
sources[count++] = &source;
}
sink = m_sink;
reset = m_owner.backend != 0;
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
}
for (unsigned i = count; i > 0; --i)
sources[i - 1]->endpoint->Stop();
if (reset && sink)
sink->Reset();
CSRWExclusiveLock lock(m_lock);
m_sink = nullptr;
m_sinkState = 0;
}
bool CInputHub::SourceValid(const SourceKey& source) const
{
if (!source.client || !source.generation)
return false;
return BindingValid(source);
}
bool CInputHub::BindingValid(const SourceKey& source) const
{
if (!BindingPresent(source))
return false;
for (const Source& slot : m_sources)
if (slot.active && !slot.reserved && slot.endpoint &&
slot.backend == source.backend &&
slot.epoch == source.epoch)
return true;
return false;
}
bool CInputHub::BindingPresent(const SourceKey& source) const
{
if (!source.backend || !source.epoch)
return false;
for (const Source& slot : m_sources)
if (slot.active && slot.endpoint &&
slot.backend == source.backend &&
slot.epoch == source.epoch)
return true;
return false;
}
bool CInputHub::OwnerValid(const SourceKey& source) const
{
return SourceValid(source) && m_owner == source;
}
void CInputHub::ClearInteraction()
{
m_interactionOwner = {};
m_interactionDeadline = 0;
}
void CInputHub::InvalidateInteraction()
{
ClearInteraction();
AdvanceInteractionSerial();
}
void CInputHub::AdvanceInteractionSerial()
{
Seq::Inc(m_interactionSerial);
}
bool CInputHub::CheckState()
{
if (!m_started || !m_sink)
return false;
const uint64_t state = m_sink->GetState();
if (state != m_sinkState)
{
if (m_owner.backend)
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
m_sinkState = state;
}
else if (m_owner.backend && GetTickCount64() >= m_ownerDeadline)
{
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
}
return (state & 1) != 0;
}
InputTargetState CInputHub::GetState(const SourceKey& source)
{
CSRWExclusiveLock lock(m_lock);
InputTargetState result;
if (!BindingPresent(source))
return result;
const bool available = CheckState();
result.state = m_sinkState;
if (!available)
return result;
if (!BindingValid(source))
return result;
if (!m_owner.backend)
result.available = true;
else if (source.client && source.generation && m_owner == source)
{
result.available = true;
result.owned = true;
result.owner.client = m_owner.client;
result.owner.generation = m_owner.generation;
}
return result;
}
void CInputHub::Failed(Source& source)
{
CSRWExclusiveLock lock(m_lock);
if (!source.active)
return;
if (m_owner.backend == source.backend && m_owner.epoch == source.epoch)
{
if (m_sink)
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
}
if (m_interactionOwner.backend == source.backend &&
m_interactionOwner.epoch == source.epoch)
{
ClearInteraction();
}
AdvanceInteractionSerial();
source.active = false;
source.failed = true;
source.failurePending = true;
}
InputResult CInputHub::Claim(const SourceKey& source)
{
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (!CheckState())
return InputResult::UNAVAILABLE;
if (m_owner.backend)
return m_owner == source ?
InputResult::ACCEPTED : InputResult::BUSY;
if (!m_sink->Reset() || m_sink->GetState() != m_sinkState)
{
const uint64_t state = m_sink->GetState();
m_sinkState = state;
InvalidateInteraction();
return InputResult::UNAVAILABLE;
}
m_owner = source;
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
InvalidateInteraction();
return InputResult::ACCEPTED;
}
InputResult CInputHub::Touch(const SourceKey& source)
{
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (!CheckState())
return InputResult::UNAVAILABLE;
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
return InputResult::ACCEPTED;
}
InputResult CInputHub::Release(const SourceKey& source, bool reset)
{
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
bool accepted = true;
if (reset && m_sink)
accepted = m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
return accepted ? InputResult::ACCEPTED : InputResult::UNAVAILABLE;
}
InputResult CInputHub::SendMouseRelative(const SourceKey& source,
int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons)
{
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (!CheckState())
return InputResult::UNAVAILABLE;
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
if (!m_sink->SendMouseRelative(deltaX, deltaY, wheel, buttons))
{
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
return InputResult::UNAVAILABLE;
}
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
return InputResult::ACCEPTED;
}
InputResult CInputHub::SendMouseAbsolute(const SourceKey& source,
uint16_t x, uint16_t y, int32_t wheel, uint32_t buttons)
{
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (!CheckState())
return InputResult::UNAVAILABLE;
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
if (!m_sink->SendMouseAbsolute(x, y, wheel, buttons))
{
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
return InputResult::UNAVAILABLE;
}
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
return InputResult::ACCEPTED;
}
InputResult CInputHub::SendKeyboard(const SourceKey& source,
uint8_t modifiers, const uint8_t * keys)
{
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (!CheckState())
return InputResult::UNAVAILABLE;
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
if (!m_sink->SendKeyboard(modifiers, keys))
{
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
return InputResult::UNAVAILABLE;
}
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
return InputResult::ACCEPTED;
}
InputResult CInputHub::Reset(const SourceKey& source)
{
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (!CheckState())
return InputResult::UNAVAILABLE;
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
if (!m_sink->Reset())
{
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
return InputResult::UNAVAILABLE;
}
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
return InputResult::ACCEPTED;
}