mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 23:21:29 +00:00
Report guest HID keyboard LED state from LGInput to LGIdd and publish it with the LGMP input endpoint status. Forward SPICE modifier updates through the same provider state so evdev can synchronize physical keyboard LEDs across transport changes and reconnects. Bump the versioned pipe and KVMFR input status contracts for the new feedback state. Accept earlier LGMP input status versions without LED feedback so the client retains input while the guest driver is being upgraded.
728 lines
19 KiB
C++
728 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;
|
|
result.keyboardLEDsValid = m_sink->GetKeyboardLEDs(
|
|
result.keyboardLEDs);
|
|
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;
|
|
}
|