[client/idd] input: reduce network latency

Route input through a dedicated high-priority stream, send pointer
motion as cumulative datagrams, and recover suspected loss reliably.

Reduce downstream input queuing with age-aware coalescing and input
thread scheduling.
This commit is contained in:
Geoffrey McRae
2026-10-08 20:34:04 +11:00
parent e30afaca2d
commit 1af2f13440
17 changed files with 3359 additions and 759 deletions

View File

@@ -29,6 +29,7 @@
#include "CSRWLock.h"
#include "InputPipeProtocol.h"
#include <avrt.h>
#include <string.h>
CInputPipeServer g_inputPipeServer;
@@ -43,9 +44,18 @@ bool CInputPipeServer::Init()
m_performanceFrequency.QuadPart = 0;
if (!QueryPerformanceFrequency(&m_performanceFrequency))
m_performanceFrequency.QuadPart = 0;
m_lastStatistics = GetTickCount64();
m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
m_queueEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
static_assert(MOTION_AGE_BUDGET_MS &&
1000 % MOTION_AGE_BUDGET_MS == 0,
"motion age budget must evenly divide one second");
const int64_t budgetDivisor = 1000 / MOTION_AGE_BUDGET_MS;
m_motionBudgetTicks =
m_performanceFrequency.QuadPart / budgetDivisor;
if (m_performanceFrequency.QuadPart % budgetDivisor)
++m_motionBudgetTicks;
m_lastStatistics = GetTickCount64();
m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
m_queueEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (!m_stopEvent || !m_queueEvent)
{
DEBUG_ERROR_HR(GetLastError(),
@@ -119,6 +129,89 @@ void CInputPipeServer::DeInit()
m_statSlowWrites = 0;
m_statWriteTicks = 0;
m_statMaxWriteTicks = 0;
m_motionBudgetTicks = 0;
m_performanceFrequency.QuadPart = 0;
}
bool CInputPipeServer::MotionBudgetExceededLocked() const
{
if (m_queueCount >= MOTION_EMERGENCY_DEPTH)
return true;
if (!m_queueCount || !m_motionBudgetTicks)
return false;
LARGE_INTEGER now = {};
if (!QueryPerformanceCounter(&now))
return false;
const int64_t oldest = m_queue[m_queueHead].enqueueTicks;
return oldest > 0 && now.QuadPart >= oldest &&
now.QuadPart - oldest >= m_motionBudgetTicks;
}
CInputPipeServer::QueueItem& CInputPipeServer::GetQueueItemLocked(
size_t position)
{
return m_queue[(m_queueHead + position) % QUEUE_LENGTH];
}
void CInputPipeServer::RemoveQueueItemLocked(size_t position)
{
for (size_t i = position; i + 1 < m_queueCount; ++i)
GetQueueItemLocked(i) = GetQueueItemLocked(i + 1);
--m_queueCount;
}
bool CInputPipeServer::CompactQueuedMotionLocked()
{
for (size_t i = 0; i + 1 < m_queueCount; ++i)
{
QueueItem& first = GetQueueItemLocked(i);
QueueItem& second = GetQueueItemLocked(i + 1);
if (!first.pureMotion || !second.pureMotion ||
first.type != second.type || first.state != second.state)
continue;
if (first.type == LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE)
{
if (first.payload.mouseRelative.buttons !=
second.payload.mouseRelative.buttons ||
first.payload.mouseRelative.wheel ||
second.payload.mouseRelative.wheel)
continue;
const int64_t x = static_cast<int64_t>(
first.payload.mouseRelative.deltaX) +
second.payload.mouseRelative.deltaX;
const int64_t y = static_cast<int64_t>(
first.payload.mouseRelative.deltaY) +
second.payload.mouseRelative.deltaY;
if (x < LG_INPUT_MOUSE_DELTA_MIN || x > LG_INPUT_MOUSE_DELTA_MAX ||
y < LG_INPUT_MOUSE_DELTA_MIN || y > LG_INPUT_MOUSE_DELTA_MAX)
continue;
first.payload.mouseRelative.deltaX = static_cast<int32_t>(x);
first.payload.mouseRelative.deltaY = static_cast<int32_t>(y);
RemoveQueueItemLocked(i + 1);
++m_statRelativeCoalesced;
return true;
}
if (first.type != LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE ||
first.payload.mouseAbsolute.buttons !=
second.payload.mouseAbsolute.buttons ||
first.payload.mouseAbsolute.wheel ||
second.payload.mouseAbsolute.wheel)
continue;
first.payload.mouseAbsolute.x = second.payload.mouseAbsolute.x;
first.payload.mouseAbsolute.y = second.payload.mouseAbsolute.y;
RemoveQueueItemLocked(i + 1);
++m_statAbsoluteCoalesced;
return true;
}
return false;
}
bool CInputPipeServer::QueueLocked(
@@ -126,20 +219,26 @@ bool CInputPipeServer::QueueLocked(
const KVMFRInputPayload& payload,
bool pureMotion)
{
const bool motionBudgetExceeded = MotionBudgetExceededLocked();
if (motionBudgetExceeded)
while (CompactQueuedMotionLocked())
{
}
if (m_queueCount)
{
const size_t tailIndex =
(m_queueHead + m_queueCount - 1) % QUEUE_LENGTH;
QueueItem& tail = m_queue[tailIndex];
QueueItem& tail = m_queue[tailIndex];
if (tail.type == type)
{
if (type == LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE &&
m_queueCount >= MOTION_COALESCE_THRESHOLD &&
tail.pureMotion && pureMotion &&
tail.payload.mouseRelative.wheel == 0 &&
payload.mouseRelative.wheel == 0 &&
tail.payload.mouseRelative.buttons ==
payload.mouseRelative.buttons)
payload.mouseRelative.buttons &&
motionBudgetExceeded)
{
const int64_t x = static_cast<int64_t>(
tail.payload.mouseRelative.deltaX) +
@@ -181,16 +280,23 @@ bool CInputPipeServer::QueueRawLocked(
const KVMFRInputPayload& payload,
bool pureMotion)
{
if (MotionBudgetExceededLocked())
while (CompactQueuedMotionLocked())
{
}
if (m_queueCount == QUEUE_LENGTH)
return false;
const size_t index =
const size_t index =
(m_queueHead + m_queueCount) % QUEUE_LENGTH;
m_queue[index].type = type;
m_queue[index].state =
LARGE_INTEGER enqueueTime = {};
QueryPerformanceCounter(&enqueueTime);
m_queue[index].type = type;
m_queue[index].state =
Atomic::Load(m_state, std::memory_order_relaxed);
m_queue[index].payload = payload;
m_queue[index].pureMotion = pureMotion;
m_queue[index].enqueueTicks = enqueueTime.QuadPart;
m_queue[index].payload = payload;
m_queue[index].pureMotion = pureMotion;
++m_queueCount;
++m_statEnqueued;
if (m_queueCount > m_statQueueHighWater)
@@ -564,6 +670,19 @@ DWORD WINAPI CInputPipeServer::ThreadProc(void * context)
void CInputPipeServer::Thread()
{
DWORD avTask = 0;
HANDLE avTaskHandle = nullptr;
bool prioritySet = false;
avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask);
if (avTaskHandle)
prioritySet =
AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH) != FALSE;
if (!prioritySet && !SetThreadPriority(
GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL))
DEBUG_WARN_HR(GetLastError(),
"Failed to raise LGInput sender thread priority");
const HANDLE handles[] = { m_stopEvent, m_queueEvent };
for (;;)
{
@@ -588,6 +707,8 @@ void CInputPipeServer::Thread()
}
Invalidate(0, false);
if (avTaskHandle)
AvRevertMmThreadCharacteristics(avTaskHandle);
}
void CInputPipeServer::OnPipeConnected()

View File

@@ -32,14 +32,16 @@
class CInputPipeServer : public IInputSink, private IPipeEndpointHandler
{
private:
static constexpr size_t QUEUE_LENGTH = 128;
static constexpr size_t MOTION_COALESCE_THRESHOLD = QUEUE_LENGTH / 2;
static constexpr DWORD STATISTICS_INTERVAL_MS = 5000;
static constexpr size_t QUEUE_LENGTH = 128;
static constexpr size_t MOTION_EMERGENCY_DEPTH = 8;
static constexpr DWORD MOTION_AGE_BUDGET_MS = 1;
static constexpr DWORD STATISTICS_INTERVAL_MS = 5000;
struct QueueItem
{
KVMFRInputPayload payload;
uint64_t state;
int64_t enqueueTicks;
LGInputPipeMessageType type;
bool pureMotion;
};
@@ -84,15 +86,20 @@ private:
uint64_t m_statResyncDiscarded = 0;
size_t m_statQueueHighWater = 0;
uint64_t m_statSent = 0;
uint64_t m_statStale = 0;
uint64_t m_statWriteFailed = 0;
uint64_t m_statSlowWrites = 0;
int64_t m_statWriteTicks = 0;
int64_t m_statMaxWriteTicks = 0;
ULONGLONG m_lastStatistics = 0;
uint64_t m_statSent = 0;
uint64_t m_statStale = 0;
uint64_t m_statWriteFailed = 0;
uint64_t m_statSlowWrites = 0;
int64_t m_statWriteTicks = 0;
int64_t m_statMaxWriteTicks = 0;
int64_t m_motionBudgetTicks = 0;
ULONGLONG m_lastStatistics = 0;
LARGE_INTEGER m_performanceFrequency = {};
bool MotionBudgetExceededLocked() const;
QueueItem& GetQueueItemLocked(size_t position);
void RemoveQueueItemLocked(size_t position);
bool CompactQueuedMotionLocked();
bool QueueLocked(LGInputPipeMessageType type,
const KVMFRInputPayload& payload, bool pureMotion);
bool QueueRawLocked(LGInputPipeMessageType type,

View File

@@ -207,29 +207,31 @@ bool CInputHub::Bind(
void CInputHub::Unbind(BackendId backend, uint32_t epoch)
{
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
Source * selected = nullptr;
IInputSource * input = nullptr;
bool stop = false;
Source * selected = nullptr;
IInputSource * input = nullptr;
bool stop = false;
{
CSRWExclusiveLock authorityLock(m_authorityLock);
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.active = false;
source.failed = false;
source.failurePending = false;
source.reserved = true;
input = source.endpoint;
stop = source.running;
source.running = false;
selected = &source;
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_owner = {};
m_ownerDeadline = 0;
AdvanceAuthorityGeneration();
}
if (m_interactionOwner.backend == backend &&
m_interactionOwner.epoch == epoch)
@@ -246,16 +248,17 @@ void CInputHub::Unbind(BackendId backend, uint32_t epoch)
if (input && stop)
input->Stop();
CSRWExclusiveLock authorityLock(m_authorityLock);
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->backend = 0;
selected->epoch = 0;
selected->endpoint = nullptr;
selected->running = false;
selected->reserved = false;
selected->failed = false;
selected->failurePending = false;
}
}
@@ -267,6 +270,7 @@ InteractionResult CInputHub::CheckInteraction(
if (!source.backend || !source.epoch || !source.client)
return InteractionResult::STALE;
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (m_started && m_sink)
CheckState();
@@ -313,6 +317,7 @@ void CInputHub::CommitInteraction(
source != permit.source)
return;
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (m_started && m_sink)
CheckState();
@@ -367,9 +372,10 @@ bool CInputHub::TakeFailure(SourceKey& source)
bool CInputHub::Start(IInputSink& sink)
{
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
Source * sources[MAX_SOURCES] = {};
unsigned count = 0;
Source * sources[MAX_SOURCES] = {};
unsigned count = 0;
{
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (m_started)
return m_sink == &sink;
@@ -387,10 +393,11 @@ bool CInputHub::Start(IInputSink& sink)
for (unsigned i = 0; i < count; ++i)
{
Source& source = *sources[i];
Source& source = *sources[i];
const bool started = source.endpoint->Start(source);
bool stop = !started;
bool stop = !started;
{
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (!started || source.failed || !source.active || !source.reserved)
{
@@ -399,8 +406,9 @@ bool CInputHub::Start(IInputSink& sink)
{
if (m_sink)
m_sink->Reset();
m_owner = {};
m_owner = {};
m_ownerDeadline = 0;
AdvanceAuthorityGeneration();
}
if (m_interactionOwner.backend == source.backend &&
m_interactionOwner.epoch == source.epoch)
@@ -432,11 +440,12 @@ bool CInputHub::Start(IInputSink& sink)
void CInputHub::Stop()
{
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
Source * sources[MAX_SOURCES] = {};
unsigned count = 0;
IInputSink * sink = nullptr;
bool reset = false;
Source * sources[MAX_SOURCES] = {};
unsigned count = 0;
IInputSink * sink = nullptr;
bool reset = false;
{
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (!m_started)
{
@@ -450,10 +459,12 @@ void CInputHub::Stop()
source.running = false;
sources[count++] = &source;
}
sink = m_sink;
reset = m_owner.backend != 0;
sink = m_sink;
reset = m_owner.backend != 0;
m_owner = {};
m_ownerDeadline = 0;
if (reset)
AdvanceAuthorityGeneration();
InvalidateInteraction();
}
@@ -462,6 +473,7 @@ void CInputHub::Stop()
if (reset && sink)
sink->Reset();
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
m_sink = nullptr;
m_sinkState = 0;
@@ -520,6 +532,41 @@ void CInputHub::AdvanceInteractionSerial()
Seq::Inc(m_interactionSerial);
}
void CInputHub::AdvanceAuthorityGeneration()
{
Seq::Inc(m_authorityGeneration);
AdvanceInputGeneration();
}
void CInputHub::AdvanceInputGeneration()
{
Seq::Inc(m_inputGeneration);
}
bool CInputHub::StateAvailable() const
{
if (!m_started || !m_sink)
return false;
const uint64_t state = m_sink->GetState();
if (state != m_sinkState || !(state & 1))
return false;
return !m_owner.backend || GetTickCount64() < m_ownerDeadline;
}
InputResult CInputHub::ReconcileAuthorityState(const SourceKey& source)
{
CSRWExclusiveLock authorityLock(m_authorityLock);
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;
return InputResult::ACCEPTED;
}
bool CInputHub::CheckState()
{
if (!m_started || !m_sink)
@@ -529,7 +576,10 @@ bool CInputHub::CheckState()
if (state != m_sinkState)
{
if (m_owner.backend)
{
m_sink->Reset();
AdvanceAuthorityGeneration();
}
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
@@ -540,13 +590,31 @@ bool CInputHub::CheckState()
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
AdvanceAuthorityGeneration();
InvalidateInteraction();
}
return (state & 1) != 0;
}
void CInputHub::HandleSinkFailure(
const SourceKey& source, bool reset, uint64_t inputGeneration)
{
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (inputGeneration != m_inputGeneration || !OwnerValid(source))
return;
if (reset && m_sink)
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
AdvanceAuthorityGeneration();
InvalidateInteraction();
}
InputTargetState CInputHub::GetState(const SourceKey& source)
{
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
InputTargetState result;
if (!BindingPresent(source))
@@ -563,8 +631,8 @@ InputTargetState CInputHub::GetState(const SourceKey& source)
result.available = true;
else if (source.client && source.generation && m_owner == source)
{
result.available = true;
result.owned = true;
result.available = true;
result.owned = true;
result.owner.client = m_owner.client;
result.owner.generation = m_owner.generation;
}
@@ -577,16 +645,43 @@ bool CInputHub::WithAuthority(const SourceKey& source,
if (!action)
return false;
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source) || !CheckState() || !OwnerValid(source))
return false;
for (;;)
{
// Freeze ownership while allowing the input fast path to continue under
// the shared authority guard. The action must not hold the state lock.
CSRWSharedLock authorityLock(m_authorityLock);
uint64_t generation = 0;
{
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return false;
if (StateAvailable())
{
if (!OwnerValid(source))
return false;
generation = m_authorityGeneration;
}
}
action(opaque);
return true;
if (!generation)
{
authorityLock.Unlock();
if (ReconcileAuthorityState(source) != InputResult::ACCEPTED)
return false;
continue;
}
action(opaque);
CSRWExclusiveLock lock(m_lock);
return generation == m_authorityGeneration &&
SourceValid(source) && OwnerValid(source);
}
}
void CInputHub::Failed(Source& source)
{
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (!source.active)
return;
@@ -594,8 +689,9 @@ void CInputHub::Failed(Source& source)
{
if (m_sink)
m_sink->Reset();
m_owner = {};
m_owner = {};
m_ownerDeadline = 0;
AdvanceAuthorityGeneration();
}
if (m_interactionOwner.backend == source.backend &&
m_interactionOwner.epoch == source.epoch)
@@ -610,6 +706,7 @@ void CInputHub::Failed(Source& source)
InputResult CInputHub::Claim(const SourceKey& source)
{
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
@@ -627,25 +724,38 @@ InputResult CInputHub::Claim(const SourceKey& source)
}
m_owner = source;
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
AdvanceAuthorityGeneration();
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;
for (;;)
{
{
CSRWSharedLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (StateAvailable())
{
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
return InputResult::ACCEPTED;
}
}
const InputResult result = ReconcileAuthorityState(source);
if (result != InputResult::ACCEPTED)
return result;
}
}
InputResult CInputHub::Release(const SourceKey& source, bool reset)
{
CSRWExclusiveLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
@@ -656,6 +766,7 @@ InputResult CInputHub::Release(const SourceKey& source, bool reset)
accepted = m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
AdvanceAuthorityGeneration();
InvalidateInteraction();
return accepted ? InputResult::ACCEPTED : InputResult::UNAVAILABLE;
}
@@ -663,85 +774,134 @@ InputResult CInputHub::Release(const SourceKey& source, bool reset)
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))
for (;;)
{
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
return InputResult::UNAVAILABLE;
{
CSRWSharedLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (StateAvailable())
{
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
const uint64_t inputGeneration = m_inputGeneration;
if (!m_sink->SendMouseRelative(deltaX, deltaY, wheel, buttons))
{
lock.Unlock();
authorityLock.Unlock();
HandleSinkFailure(source, true, inputGeneration);
return InputResult::UNAVAILABLE;
}
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
AdvanceInputGeneration();
return InputResult::ACCEPTED;
}
}
const InputResult result = ReconcileAuthorityState(source);
if (result != InputResult::ACCEPTED)
return result;
}
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))
for (;;)
{
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
return InputResult::UNAVAILABLE;
{
CSRWSharedLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (StateAvailable())
{
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
const uint64_t inputGeneration = m_inputGeneration;
if (!m_sink->SendMouseAbsolute(x, y, wheel, buttons))
{
lock.Unlock();
authorityLock.Unlock();
HandleSinkFailure(source, true, inputGeneration);
return InputResult::UNAVAILABLE;
}
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
AdvanceInputGeneration();
return InputResult::ACCEPTED;
}
}
const InputResult result = ReconcileAuthorityState(source);
if (result != InputResult::ACCEPTED)
return result;
}
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))
for (;;)
{
m_sink->Reset();
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
return InputResult::UNAVAILABLE;
{
CSRWSharedLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (StateAvailable())
{
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
const uint64_t inputGeneration = m_inputGeneration;
if (!m_sink->SendKeyboard(modifiers, keys))
{
lock.Unlock();
authorityLock.Unlock();
HandleSinkFailure(source, true, inputGeneration);
return InputResult::UNAVAILABLE;
}
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
AdvanceInputGeneration();
return InputResult::ACCEPTED;
}
}
const InputResult result = ReconcileAuthorityState(source);
if (result != InputResult::ACCEPTED)
return result;
}
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())
for (;;)
{
m_owner = {};
m_ownerDeadline = 0;
InvalidateInteraction();
return InputResult::UNAVAILABLE;
{
CSRWSharedLock authorityLock(m_authorityLock);
CSRWExclusiveLock lock(m_lock);
if (!SourceValid(source))
return InputResult::STALE;
if (StateAvailable())
{
if (!OwnerValid(source))
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
const uint64_t inputGeneration = m_inputGeneration;
if (!m_sink->Reset())
{
lock.Unlock();
authorityLock.Unlock();
HandleSinkFailure(source, false, inputGeneration);
return InputResult::UNAVAILABLE;
}
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
AdvanceInputGeneration();
return InputResult::ACCEPTED;
}
}
const InputResult result = ReconcileAuthorityState(source);
if (result != InputResult::ACCEPTED)
return result;
}
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
return InputResult::ACCEPTED;
}

View File

@@ -77,15 +77,18 @@ private:
mutable CSRWLock m_lock;
CSRWLock m_lifecycleLock;
CSRWLock m_authorityLock;
Source m_sources[MAX_SOURCES];
IInputSink * m_sink = nullptr;
uint64_t m_sinkState = 0;
IInputSink * m_sink = nullptr;
uint64_t m_sinkState = 0;
SourceKey m_owner;
uint64_t m_ownerDeadline = 0;
uint64_t m_ownerDeadline = 0;
uint64_t m_authorityGeneration = 1;
uint64_t m_inputGeneration = 1;
SourceKey m_interactionOwner;
uint64_t m_interactionDeadline = 0;
uint64_t m_interactionSerial = 1;
bool m_started = false;
uint64_t m_interactionDeadline = 0;
uint64_t m_interactionSerial = 1;
bool m_started = false;
bool SourceValid(const SourceKey& source) const;
bool BindingPresent(const SourceKey& source) const;
@@ -94,7 +97,13 @@ private:
void ClearInteraction();
void InvalidateInteraction();
void AdvanceInteractionSerial();
void AdvanceAuthorityGeneration();
void AdvanceInputGeneration();
bool StateAvailable() const;
InputResult ReconcileAuthorityState(const SourceKey& source);
bool CheckState();
void HandleSinkFailure(const SourceKey& source, bool reset,
uint64_t inputGeneration);
InputTargetState GetState(const SourceKey& source);
bool WithAuthority(const SourceKey& source,
InputAuthorityAction action, void * opaque);

File diff suppressed because it is too large Load Diff