/** * 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/CTransportManager.h" #include "CDebug.h" #include #include #include static const uint64_t RETRY_DELAY_MS = 500; class CSourceEvents final : public ITransportEvents { private: BackendId m_backend; uint32_t m_epoch; ITransportEvents& m_events; SourceKey Stamp(const SourceKey& source) const { SourceKey stamped = source; stamped.backend = m_backend; stamped.epoch = m_epoch; return stamped; } public: CSourceEvents( BackendId backend, uint32_t epoch, ITransportEvents& events) : m_backend(backend), m_epoch(epoch), m_events(events) {} void OnSetCursorPos( const SourceKey& source, int32_t x, int32_t y) override { m_events.OnSetCursorPos(Stamp(source), x, y); } void OnSetResolution(const SourceKey& source, uint32_t width, uint32_t height) override { m_events.OnSetResolution(Stamp(source), width, height); } void OnRecoveryRequest(const SourceKey& source, uint64_t session, uint32_t serial, bool active) override { m_events.OnRecoveryRequest( Stamp(source), session, serial, active); } }; CTransportManager::~CTransportManager() { Stop(); } bool CTransportManager::Add(BackendId id, const char * name, bool required, bool primary, CreateFn create) { if (!id || !name || !create || (primary && m_primary)) return false; for (const auto& current : m_entries) if (current->id == id) return false; std::unique_ptr entry(new (std::nothrow) Entry); if (!entry) return false; entry->id = id; entry->name = name; entry->required = required; entry->primary = primary; entry->create = create; Entry * raw = entry.get(); m_entries.push_back(std::move(entry)); if (primary) m_primary = raw; return true; } ITransport::OpenResult CTransportManager::OpenEntry(Entry& entry) { if (!entry.transport) entry.transport = entry.create(); if (!entry.transport) { entry.state = State::FAILED; return OpenResult::FAILURE; } const OpenResult result = entry.transport->Open(); switch (result) { case OpenResult::SUCCESS: entry.state = State::OPEN; break; case OpenResult::RETRY: ScheduleRetry(entry); break; case OpenResult::FAILURE: entry.state = State::FAILED; break; } return result; } bool CTransportManager::InitializeEntry(Entry& entry) { if (entry.state == State::INITIALIZED || entry.state == State::READY) return true; if (entry.state != State::OPEN || !entry.transport->Initialize()) { entry.state = State::FAILED; return false; } if (!m_control.Add(entry.id, entry.epoch, entry.transport->Control())) { entry.state = State::FAILED; return false; } entry.controlAdded = true; entry.state = State::INITIALIZED; return true; } bool CTransportManager::SetupEntry(Entry& entry) { if (entry.state == State::READY) return true; if (entry.state != State::INITIALIZED || !entry.transport->Setup(m_alignment)) { entry.state = State::FAILED; return false; } entry.state = State::READY; return true; } void CTransportManager::ScheduleRetry(Entry& entry) { entry.state = State::RETRY; entry.retryAt = GetTickCount64() + RETRY_DELAY_MS; } ITransport::OpenResult CTransportManager::Open() { if (!m_primary) return OpenResult::FAILURE; OpenResult aggregate = OpenResult::SUCCESS; for (const auto& current : m_entries) { Entry& entry = *current; if (entry.state == State::OPEN || entry.state == State::INITIALIZED || entry.state == State::READY) continue; const OpenResult result = OpenEntry(entry); if (!entry.required || result == OpenResult::SUCCESS) continue; if (result == OpenResult::FAILURE) return OpenResult::FAILURE; aggregate = OpenResult::RETRY; } return aggregate; } bool CTransportManager::Initialize() { for (const auto& current : m_entries) { Entry& entry = *current; if (entry.state != State::OPEN) continue; if (!InitializeEntry(entry)) { if (entry.required) return false; ScheduleRetry(entry); } } m_initialized = true; return m_primary && (m_primary->state == State::INITIALIZED || m_primary->state == State::READY); } bool CTransportManager::Setup(size_t alignment) { if (!m_initialized || !m_primary) return false; m_alignment = alignment; if (!SetupEntry(*m_primary)) return false; m_setup = true; return true; } void CTransportManager::RetryEntry(Entry& entry, uint64_t now) { if (entry.state != State::RETRY || now < entry.retryAt) return; if (entry.primary && m_exposed) return; RemoveServices(entry); if (entry.transport) entry.transport->Stop(); entry.transport.reset(); ++entry.epoch; if (!entry.epoch) ++entry.epoch; if (OpenEntry(entry) != OpenResult::SUCCESS) return; if (m_initialized && !InitializeEntry(entry)) { ScheduleRetry(entry); return; } if (m_setup && entry.primary && !SetupEntry(entry)) ScheduleRetry(entry); } void CTransportManager::HandleProcessResult( Entry& entry, ProcessResult result) { if (result == ProcessResult::OK) return; if (entry.primary && m_exposed) { DEBUG_WARN("Transport %s requested a restart while its frame interfaces " "are active", entry.name); return; } if (result == ProcessResult::RETRY || !entry.required) { RemoveServices(entry); ScheduleRetry(entry); return; } RemoveServices(entry); entry.state = State::FAILED; } ITransport::ProcessResult CTransportManager::Process( ITransportEvents& events) { const uint64_t now = GetTickCount64(); for (const auto& current : m_entries) { Entry& entry = *current; RetryEntry(entry, now); if (entry.state != State::INITIALIZED && entry.state != State::READY) continue; CSourceEvents sourceEvents(entry.id, entry.epoch, events); const ProcessResult result = entry.transport->Process(sourceEvents); HandleProcessResult(entry, result); } return ProcessResult::OK; } void CTransportManager::Stop() { for (auto current = m_entries.rbegin(); current != m_entries.rend(); ++current) { Entry& entry = **current; RemoveServices(entry); if (entry.transport && entry.state != State::STOPPED) entry.transport->Stop(); entry.state = State::STOPPED; } } void CTransportManager::RemoveServices(Entry& entry) { if (entry.controlAdded) { m_control.Remove(entry.id, entry.epoch); entry.controlAdded = false; } } void CTransportManager::SyncRecovery() { for (const auto& current : m_entries) if (current->transport && (current->state == State::INITIALIZED || current->state == State::READY)) current->transport->SyncRecovery(); } void CTransportManager::RecoveryStatus(uint64_t session, uint32_t serial, bool active, Recovery state, uint32_t error) { for (const auto& current : m_entries) if (current->transport && (current->state == State::INITIALIZED || current->state == State::READY)) current->transport->RecoveryStatus( session, serial, active, state, error); } ITransport& CTransportManager::Primary() { m_exposed = true; return *m_primary->transport; } FrameMemoryLimits CTransportManager::GetMemoryLimits() const { return m_primary->transport->GetMemoryLimits(); } DirectFrameBufferMemory CTransportManager::GetDirectMemory() const { return m_primary->transport->GetDirectMemory(); } IFrameTransport& CTransportManager::Frames() { return Primary().Frames(); } IControlTransport& CTransportManager::Control() { return m_control; } IInputTransport * CTransportManager::Input() { return Primary().Input(); }