/** * 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; }