/** * 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 #include "transport/lgmp/CLGMPInputTransport.h" #include #include "config/CSettings.h" #include "transport/lgmp/CLGMPHost.h" #include "Atomic.h" #include "CDebug.h" #include "CSRWLock.h" #include "Seq.h" #include "common/KVMFRInput.h" #include "common/LGMPConfig.h" #include #include #ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION #define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002 #endif static const LGMPQueueConfig INPUT_QUEUE_CONFIG = { LGMP_Q_INPUT, LGMP_Q_INPUT_LEN, 1000, }; static constexpr int32_t MAX_SPLIT_REPORTS = 4; static constexpr int32_t MAX_MOUSE_DELTA = INT16_MAX * MAX_SPLIT_REPORTS; static constexpr int32_t MIN_MOUSE_DELTA = INT16_MIN * MAX_SPLIT_REPORTS; static constexpr int32_t MAX_MOUSE_WHEEL = INT8_MAX * MAX_SPLIT_REPORTS; static constexpr int32_t MIN_MOUSE_WHEEL = -INT8_MAX * MAX_SPLIT_REPORTS; static bool IsZero(const void * data, size_t size) { const uint8_t * byte = static_cast(data); for (size_t i = 0; i < size; ++i) if (byte[i]) return false; return true; } static bool ArmPollTimer(HANDLE timer, uint32_t waitUs) { LARGE_INTEGER due = {}; due.QuadPart = -static_cast(waitUs) * 10; return SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE) != FALSE; } static KVMFRStreamDescriptor ExportStreamDescriptor( const LGMPStreamDescriptor& source) { KVMFRStreamDescriptor result = {}; result.magic = source.magic; result.version = source.version; result.size = source.size; result.offset = source.offset; result.regionSize = source.regionSize; result.direction = source.direction; result.policy = source.policy; result.slotCount = source.slotCount; result.slotSize = source.slotSize; return result; } CLGMPInputTransport::~CLGMPInputTransport() { DeInit(); } bool CLGMPInputTransport::Initialize() { if (m_queue) return true; LGMP_STATUS status = m_host.CreateQueue( INPUT_QUEUE_CONFIG, &m_queue); if (status != LGMP_OK) { DEBUG_ERROR("lgmpHostQueueCreate Failed (Input): %s", lgmpStatusString(status)); return false; } const LGMPStreamConfig streamConfig = { LGMP_STREAM_CLIENT_TO_HOST, LGMP_STREAM_RELIABLE_FIFO, KVMFR_INPUT_STREAM_SLOT_COUNT, KVMFR_INPUT_STREAM_SLOT_SIZE, }; for (StreamEndpoint& endpoint : m_streamEndpoint) { status = m_host.CreateStream(streamConfig, &endpoint.stream); if (status != LGMP_OK) { DEBUG_ERROR("lgmpHostStreamNew Failed (Input): %s", lgmpStatusString(status)); DeInit(); return false; } lgmpHostStreamGetDescriptor(endpoint.stream, &endpoint.descriptor); } Seq::Inc(m_streamGeneration); for (PLGMPMemory& memory : m_statusMemory) { status = m_host.Allocate(sizeof(KVMFRInputStatus), &memory); if (status != LGMP_OK) { DEBUG_ERROR("lgmpHostMemAlloc Failed (Input Status): %s", lgmpStatusString(status)); DeInit(); return false; } memset(lgmpHostMemPtr(memory), 0, sizeof(KVMFRInputStatus)); } { CSRWExclusiveLock lock(m_statusLock); m_statusDirty = true; } return true; } void CLGMPInputTransport::DeInit() { Stop(); for (PLGMPMemory& memory : m_statusMemory) lgmpHostMemFree(&memory); for (StreamEndpoint& endpoint : m_streamEndpoint) { lgmpHostStreamFree(&endpoint.stream); endpoint = {}; } m_streamGeneration = 0; m_streamDrainCursor = 0; m_queue = nullptr; } bool CLGMPInputTransport::ReconcileStreams(bool& changed) { changed = false; uint32_t activeClientIDs[32] = {}; unsigned activeClientCount = 0; const LGMP_STATUS clientsStatus = lgmpHostGetClientIDs( m_queue, activeClientIDs, &activeClientCount); if (clientsStatus != LGMP_OK) { DEBUG_ERROR("lgmpHostGetClientIDs Failed (Input): %s", lgmpStatusString(clientsStatus)); return false; } CSRWExclusiveLock lock(m_streamLock); for (StreamEndpoint& endpoint : m_streamEndpoint) { if (!endpoint.clientID) continue; bool active = false; for (unsigned i = 0; i < activeClientCount; ++i) if (activeClientIDs[i] == endpoint.clientID) { active = true; break; } if (!active) { if (!endpoint.draining) { endpoint.draining = true; changed = true; } const LGMP_STATUS status = lgmpHostStreamUnbind(endpoint.stream); if (status == LGMP_ERR_STREAM_BUSY) { // A vanished peer may have left an active reservation. Keep this // endpoint draining; force-unbind is reserved for transport teardown. continue; } if (status != LGMP_OK && status != LGMP_ERR_STREAM_UNBOUND) { DEBUG_ERROR("lgmpHostStreamUnbind Failed (Input): %s", lgmpStatusString(status)); return false; } endpoint.clientID = 0; endpoint.epoch = 0; endpoint.draining = false; changed = true; continue; } if (!endpoint.draining) continue; const LGMP_STATUS status = lgmpHostStreamUnbind(endpoint.stream); if (status == LGMP_ERR_STREAM_BUSY) continue; if (status != LGMP_OK) { DEBUG_ERROR("lgmpHostStreamUnbind Failed (Input): %s", lgmpStatusString(status)); return false; } endpoint.clientID = 0; endpoint.epoch = 0; endpoint.draining = false; changed = true; } for (unsigned i = 0; i < activeClientCount; ++i) { const uint32_t clientID = activeClientIDs[i]; bool bound = false; for (const StreamEndpoint& endpoint : m_streamEndpoint) if (endpoint.clientID == clientID) { bound = true; break; } if (bound) continue; StreamEndpoint * available = nullptr; for (StreamEndpoint& endpoint : m_streamEndpoint) if (!endpoint.clientID) { available = &endpoint; break; } if (!available) continue; uint32_t epoch = 0; const LGMP_STATUS status = lgmpHostStreamBind( available->stream, clientID, &epoch); if (status != LGMP_OK) { DEBUG_ERROR("lgmpHostStreamBind Failed (Input client %u): %s", clientID, lgmpStatusString(status)); return false; } available->clientID = clientID; available->epoch = epoch; available->draining = false; changed = true; } if (changed) Seq::Inc(m_streamGeneration); return true; } void CLGMPInputTransport::ResetStreams() { bool changed = false; { CSRWExclusiveLock lock(m_streamLock); for (StreamEndpoint& endpoint : m_streamEndpoint) { if (!endpoint.clientID) continue; const LGMP_STATUS status = lgmpHostStreamUnbind(endpoint.stream); if (status != LGMP_OK && status != LGMP_ERR_STREAM_BUSY) DEBUG_WARN("lgmpHostStreamUnbind Failed (Input): %s", lgmpStatusString(status)); if (status == LGMP_OK) { endpoint.clientID = 0; endpoint.epoch = 0; endpoint.draining = false; } else if (status == LGMP_ERR_STREAM_BUSY) { // Stop is restartable, so preserve outstanding records and finish // the graceful unbind after the transport starts again. endpoint.draining = true; } changed = true; } if (changed) Seq::Inc(m_streamGeneration); } if (changed) { CSRWExclusiveLock lock(m_statusLock); m_statusDirty = true; } } InputSourceId CLGMPInputTransport::Owner() const { InputSourceId source; source.client = m_ownerClientID; source.generation = m_ownerGeneration; return source; } void CLGMPInputTransport::UpdateTargetState( const InputTargetState& state) { CSRWExclusiveLock lock(m_statusLock); if (state.state == m_targetState.state && state.available == m_targetState.available && state.owned == m_targetState.owned && state.keyboardLEDsValid == m_targetState.keyboardLEDsValid && state.keyboardLEDs == m_targetState.keyboardLEDs && state.owner.client == m_targetState.owner.client && state.owner.generation == m_targetState.owner.generation) return; if (state.state != m_targetState.state) { Seq::Inc(m_endpointGeneration); } m_targetState = state; m_statusDirty = true; } bool CLGMPInputTransport::PublishStatus() { if (!m_queue) return true; bool streamChanged = false; if (!ReconcileStreams(streamChanged)) return false; CSRWSharedLock streamLock(m_streamLock); CSRWExclusiveLock lock(m_statusLock); if (lgmpHostQueueNewSubs(m_queue)) m_statusDirty = true; if (streamChanged) m_statusDirty = true; if (!m_statusDirty || !lgmpHostQueueHasSubs(m_queue)) return true; PLGMPMemory memory = nullptr; for (PLGMPMemory candidate : m_statusMemory) if (!lgmpHostQueuePayloadPending(m_queue, candidate)) { memory = candidate; break; } if (!memory) return true; const bool available = m_targetState.available; KVMFRInputStatus status = {}; status.version = KVMFR_INPUT_VERSION; status.capabilities = available ? KVMFR_INPUT_CAP_MOUSE_RELATIVE | KVMFR_INPUT_CAP_MOUSE_ABSOLUTE | KVMFR_INPUT_CAP_KEYBOARD : 0; status.flags = available ? KVMFR_INPUT_STATUS_AVAILABLE : 0; if (m_targetState.owned) { status.flags |= KVMFR_INPUT_STATUS_HAS_OWNER; status.ownerClientID = m_targetState.owner.client; status.ownerGeneration = m_targetState.owner.generation; } if (available && m_targetState.keyboardLEDsValid) { status.flags |= KVMFR_INPUT_STATUS_KEYBOARD_LEDS_VALID; status.keyboardLEDs = m_targetState.keyboardLEDs; } status.generation = m_endpointGeneration; status.lease = static_cast(OWNER_LEASE_MS); status.maxButtons = KVMFR_INPUT_MOUSE_BUTTON_COUNT; status.streamVersion = KVMFR_INPUT_STREAM_VERSION; status.streamEndpointCount = KVMFR_INPUT_STREAM_ENDPOINT_COUNT; status.streamGeneration = m_streamGeneration; for (unsigned i = 0; i < KVMFR_INPUT_STREAM_ENDPOINT_COUNT; ++i) { const StreamEndpoint& endpoint = m_streamEndpoint[i]; KVMFRInputStreamEndpoint& exported = status.streamEndpoint[i]; exported.stream = ExportStreamDescriptor(endpoint.descriptor); exported.flags = KVMFR_INPUT_STREAM_ENDPOINT_AVAILABLE; if (endpoint.clientID && !endpoint.draining) { exported.boundClientID = endpoint.clientID; exported.bindingGeneration = endpoint.epoch; exported.flags |= KVMFR_INPUT_STREAM_ENDPOINT_BOUND; } } memcpy(lgmpHostMemPtr(memory), &status, sizeof(status)); const uint32_t serial = Seq::Next(m_statusSerial); const LGMP_STATUS result = lgmpHostQueuePost(m_queue, serial, memory); if (result == LGMP_OK) { m_statusSerial = serial; m_statusDirty = false; } else if (result != LGMP_ERR_QUEUE_FULL) { DEBUG_WARN("lgmpHostQueuePost Failed (Input Status): %s", lgmpStatusString(result)); return false; } return true; } void CLGMPInputTransport::FlushStatus() { if (Atomic::Load(m_statusFailed, std::memory_order_acquire) || PublishStatus()) return; Atomic::Store(m_statusFailed, true, std::memory_order_release); CSRWSharedLock lock(m_lifecycleLock); if (m_stopEvent) SetEvent(m_stopEvent); } bool CLGMPInputTransport::Start(IInputTarget& target) { CSRWExclusiveLock lock(m_lifecycleLock); if (m_thread) { const DWORD state = WaitForSingleObject(m_thread, 0); if (state == WAIT_TIMEOUT) return true; if (state != WAIT_OBJECT_0) { DEBUG_ERROR_HR(GetLastError(), "Failed to inspect LGMP input worker"); return false; } CloseHandle(m_thread); CloseHandle(m_pollTimer); CloseHandle(m_stopEvent); m_thread = nullptr; m_pollTimer = nullptr; m_stopEvent = nullptr; m_target = nullptr; ResetStreams(); } if (!m_queue) return false; m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); m_pollTimer = CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); if (!m_pollTimer) m_pollTimer = CreateWaitableTimerExW( nullptr, nullptr, 0, TIMER_ALL_ACCESS); if (!m_stopEvent || !m_pollTimer) { DEBUG_ERROR_HR(GetLastError(), "Failed to create LGMP input worker resources"); if (m_pollTimer) CloseHandle(m_pollTimer); if (m_stopEvent) CloseHandle(m_stopEvent); m_pollTimer = nullptr; m_stopEvent = nullptr; return false; } m_target = ⌖ { CSRWExclusiveLock statusLock(m_statusLock); m_targetState = target.GetState({}); Seq::Inc(m_endpointGeneration); m_statusDirty = true; } Atomic::Store(m_statusFailed, false, std::memory_order_release); m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr); if (!m_thread) { DEBUG_ERROR_HR(GetLastError(), "Failed to create LGMP input worker"); m_target = nullptr; { CSRWExclusiveLock statusLock(m_statusLock); m_targetState = {}; m_statusDirty = true; } CloseHandle(m_pollTimer); CloseHandle(m_stopEvent); m_pollTimer = nullptr; m_stopEvent = nullptr; return false; } return true; } void CLGMPInputTransport::Stop() { CSRWExclusiveLock lock(m_lifecycleLock); if (m_stopEvent) SetEvent(m_stopEvent); if (m_thread) WaitForSingleObject(m_thread, INFINITE); if (m_thread) { CloseHandle(m_thread); m_thread = nullptr; } if (m_pollTimer) { CloseHandle(m_pollTimer); m_pollTimer = nullptr; } if (m_stopEvent) { CloseHandle(m_stopEvent); m_stopEvent = nullptr; } m_target = nullptr; m_ownerClientID = 0; m_ownerGeneration = 0; m_ownerSequence = 0; m_ownerDeadline = 0; ResetStreams(); { CSRWExclusiveLock statusLock(m_statusLock); m_targetState = {}; m_statusDirty = true; } } bool CLGMPInputTransport::IsOwner( uint32_t sourceClientID, uint32_t generation) const { return m_ownerClientID == sourceClientID && m_ownerGeneration == generation; } bool CLGMPInputTransport::Claim( uint32_t sourceClientID, const KVMFRInputMessage& message) { if (message.sequence != 1) { ++m_statistics.sequenceErrors; return false; } if (!m_target) { ++m_statistics.deliveryFailures; return false; } InputSourceId source; source.client = sourceClientID; source.generation = message.generation; const InputResult result = m_target->Claim(source); if (result != InputResult::ACCEPTED) { UpdateTargetState(m_target->GetState(source)); ++m_statistics.deliveryFailures; return false; } m_ownerClientID = sourceClientID; m_ownerGeneration = message.generation; m_ownerSequence = message.sequence; RenewLease(); UpdateTargetState(m_target->GetState(source)); ++m_statistics.claims; return true; } void CLGMPInputTransport::RenewLease() { m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; } void CLGMPInputTransport::ReleaseOwner(bool reset) { if (!m_ownerClientID) return; if (m_target) { m_target->Release(Owner(), reset); UpdateTargetState(m_target->GetState({})); } m_ownerClientID = 0; m_ownerGeneration = 0; m_ownerSequence = 0; m_ownerDeadline = 0; ++m_statistics.releases; } void CLGMPInputTransport::CheckOwner() { if (!m_target) return; if (m_ownerClientID) { bool found = false; bool retiring = false; { CSRWSharedLock lock(m_streamLock); for (const StreamEndpoint& endpoint : m_streamEndpoint) if (endpoint.clientID == m_ownerClientID) { found = true; retiring = endpoint.draining; break; } } if (!found || retiring) { ReleaseOwner(true); return; } } const InputTargetState state = m_target->GetState(Owner()); UpdateTargetState(state); if (!m_ownerClientID) return; if (!state.available || !state.owned) { ReleaseOwner(false); return; } if (GetTickCount64() >= m_ownerDeadline) ReleaseOwner(true); } bool CLGMPInputTransport::ValidatePayload( const KVMFRInputMessage& message) const { switch (message.type) { case KVMFR_INPUT_MESSAGE_CLAIM: case KVMFR_INPUT_MESSAGE_RELEASE: case KVMFR_INPUT_MESSAGE_KEEPALIVE: case KVMFR_INPUT_MESSAGE_RESET: return IsZero(&message.payload, sizeof(message.payload)); case KVMFR_INPUT_MESSAGE_MOUSE_RELATIVE: return message.payload.mouseRelative.deltaX >= MIN_MOUSE_DELTA && message.payload.mouseRelative.deltaX <= MAX_MOUSE_DELTA && message.payload.mouseRelative.deltaY >= MIN_MOUSE_DELTA && message.payload.mouseRelative.deltaY <= MAX_MOUSE_DELTA && message.payload.mouseRelative.wheel >= MIN_MOUSE_WHEEL && message.payload.mouseRelative.wheel <= MAX_MOUSE_WHEEL; case KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE: return message.payload.mouseAbsolute.x <= KVMFR_INPUT_MOUSE_ABSOLUTE_MAX && message.payload.mouseAbsolute.y <= KVMFR_INPUT_MOUSE_ABSOLUTE_MAX && message.payload.mouseAbsolute.wheel >= MIN_MOUSE_WHEEL && message.payload.mouseAbsolute.wheel <= MAX_MOUSE_WHEEL && !message.payload.mouseAbsolute.reserved; case KVMFR_INPUT_MESSAGE_KEYBOARD: if (!IsZero(message.payload.keyboard.reserved, sizeof(message.payload.keyboard.reserved))) return false; for (size_t i = 0; i < KVMFR_INPUT_KEYBOARD_KEY_COUNT; ++i) if (message.payload.keyboard.keys[i] > KVMFR_INPUT_KEYBOARD_USAGE_MAX) return false; return true; default: return false; } } bool CLGMPInputTransport::ProcessMessage( uint32_t sourceClientID, const KVMFRInputMessage& message) { const bool owner = IsOwner(sourceClientID, message.generation); InputSourceId source; source.client = sourceClientID; source.generation = message.generation; if (!message.generation || !message.sequence || message.reserved || !ValidatePayload(message)) { ++m_statistics.malformedMessage; if (owner) ReleaseOwner(true); return false; } if (m_target) UpdateTargetState(m_target->GetState( m_ownerClientID ? Owner() : source)); if (message.type == KVMFR_INPUT_MESSAGE_CLAIM) { if (m_ownerClientID) { if (!owner) { ++m_statistics.nonOwner; return true; } if (message.sequence == 1 && m_ownerSequence == 1) return true; ++m_statistics.sequenceErrors; ReleaseOwner(true); return false; } return Claim(sourceClientID, message); } if (!owner) { ++m_statistics.nonOwner; return true; } const uint32_t expectedSequence = Seq::Next(m_ownerSequence); if (message.sequence != expectedSequence) { ++m_statistics.sequenceErrors; ReleaseOwner(true); return false; } InputResult result = InputResult::STALE; bool inputReport = false; switch (message.type) { case KVMFR_INPUT_MESSAGE_RELEASE: ReleaseOwner(true); return true; case KVMFR_INPUT_MESSAGE_KEEPALIVE: result = m_target ? m_target->Touch(source) : InputResult::UNAVAILABLE; break; case KVMFR_INPUT_MESSAGE_RESET: result = m_target ? m_target->Reset(source) : InputResult::UNAVAILABLE; break; case KVMFR_INPUT_MESSAGE_MOUSE_RELATIVE: inputReport = true; result = m_target ? m_target->SendMouseRelative(source, message.payload.mouseRelative.deltaX, message.payload.mouseRelative.deltaY, message.payload.mouseRelative.wheel, message.payload.mouseRelative.buttons) : InputResult::UNAVAILABLE; break; case KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE: inputReport = true; result = m_target ? m_target->SendMouseAbsolute(source, message.payload.mouseAbsolute.x, message.payload.mouseAbsolute.y, message.payload.mouseAbsolute.wheel, message.payload.mouseAbsolute.buttons) : InputResult::UNAVAILABLE; break; case KVMFR_INPUT_MESSAGE_KEYBOARD: inputReport = true; result = m_target ? m_target->SendKeyboard(source, message.payload.keyboard.modifiers, message.payload.keyboard.keys) : InputResult::UNAVAILABLE; break; default: break; } if (result != InputResult::ACCEPTED) { ++m_statistics.deliveryFailures; ReleaseOwner(true); return false; } UpdateTargetState(m_target->GetState(source)); m_ownerSequence = message.sequence; RenewLease(); if (inputReport) ++m_statistics.reports; return true; } bool CLGMPInputTransport::DrainStreamMessages(bool& received) { received = false; unsigned count = 0; CSRWSharedLock lock(m_streamLock); for (; count < DRAIN_LIMIT; ++count) { StreamEndpoint * selected = nullptr; LGMPStreamBuffer buffer = {}; for (unsigned scanned = 0; scanned < KVMFR_INPUT_STREAM_ENDPOINT_COUNT; ++scanned) { const unsigned index = (m_streamDrainCursor + scanned) % KVMFR_INPUT_STREAM_ENDPOINT_COUNT; StreamEndpoint& endpoint = m_streamEndpoint[index]; if (!endpoint.clientID) continue; const LGMP_STATUS status = lgmpHostStreamReadPeek( endpoint.stream, &buffer); if (status == LGMP_ERR_STREAM_EMPTY || status == LGMP_ERR_STREAM_UNBOUND) continue; if (status != LGMP_OK) { DEBUG_ERROR("lgmpHostStreamReadPeek Failed (Input): %s", lgmpStatusString(status)); return false; } selected = &endpoint; m_streamDrainCursor = (index + 1) % KVMFR_INPUT_STREAM_ENDPOINT_COUNT; break; } if (!selected) break; received = true; ++m_statistics.messages; // Retired endpoint generations are consumed only to finish their // graceful unbind; they must not reach a newly started input target. if (!selected->draining) { if (buffer.size != sizeof(KVMFRInputMessage)) { DEBUG_WARN("Ignoring invalid KVMFR input stream message size"); ++m_statistics.malformedSize; if (selected->clientID == m_ownerClientID) ReleaseOwner(true); } else { KVMFRInputMessage message = {}; memcpy(&message, buffer.data, sizeof(message)); ProcessMessage(selected->clientID, message); } } const LGMP_STATUS releaseStatus = lgmpHostStreamReadRelease( selected->stream, &buffer); if (releaseStatus != LGMP_OK) { DEBUG_ERROR("lgmpHostStreamReadRelease Failed (Input): %s", lgmpStatusString(releaseStatus)); return false; } } if (count > m_statistics.maxDrain) m_statistics.maxDrain = count; if (count == DRAIN_LIMIT) ++m_statistics.drainLimit; return true; } void CLGMPInputTransport::LogStatistics(ULONGLONG now) { if (!m_statistics.lastLog) { m_statistics.lastLog = now; return; } if (now - m_statistics.lastLog < LOG_INTERVAL_MS) return; const Statistics statistics = m_statistics; m_statistics = {}; m_statistics.lastLog = now; if (!statistics.messages && !statistics.claims && !statistics.releases && !statistics.deliveryFailures) return; if (!g_settings.ShouldLogStatistics()) return; const double elapsed = static_cast(now - statistics.lastLog) / 1000.0; DEBUG_TRACE("LGMP input host: %.1f msg/s, %llu reports, " "drain max %u, %llu limit; %llu bad size, %llu malformed, " "%llu sequence, %llu non-owner, %llu delivery failures; " "%llu claims, %llu releases", statistics.messages / elapsed, static_cast(statistics.reports), statistics.maxDrain, static_cast(statistics.drainLimit), static_cast(statistics.malformedSize), static_cast(statistics.malformedMessage), static_cast(statistics.sequenceErrors), static_cast(statistics.nonOwner), static_cast(statistics.deliveryFailures), static_cast(statistics.claims), static_cast(statistics.releases)); } DWORD CALLBACK CLGMPInputTransport::ThreadProc(void * context) { static_cast(context)->Thread(); return 0; } void CLGMPInputTransport::Thread() { DWORD avTask = 0; HANDLE avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask); if (avTaskHandle && !AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH)) DEBUG_WARN("Failed to raise input MMCSS priority: %lu", GetLastError()); LGMPStreamPollState streamPoll = {}; LGMPStreamPollConfig pollConfig = {}; pollConfig.spinCount = 64U; pollConfig.minWaitUs = 25U; pollConfig.maxWaitUs = 1000U; const LGMP_STATUS pollStatus = lgmpStreamPollInit(&streamPoll, pollConfig); if (pollStatus != LGMP_OK) { DEBUG_ERROR("Failed to initialize LGMP input polling: %s", lgmpStatusString(pollStatus)); if (m_target) m_target->Failed(); if (avTaskHandle) AvRevertMmThreadCharacteristics(avTaskHandle); return; } m_statistics = {}; m_statistics.lastLog = GetTickCount64(); const HANDLE waitHandles[] = { m_stopEvent, m_pollTimer }; bool failed = false; for (;;) { CheckOwner(); bool streamReceived = false; if (!DrainStreamMessages(streamReceived)) { failed = true; break; } if (!PublishStatus()) { Atomic::Store(m_statusFailed, true, std::memory_order_release); failed = true; break; } if (streamReceived) lgmpStreamPollActivity(&streamPoll); const ULONGLONG now = GetTickCount64(); LogStatistics(now); const uint32_t waitUs = lgmpStreamPollIdle(&streamPoll); if (!waitUs) continue; if (!ArmPollTimer(m_pollTimer, waitUs)) { DEBUG_ERROR_HR(GetLastError(), "Failed to arm LGMP input timer"); if (WaitForSingleObject(m_stopEvent, 1) != WAIT_TIMEOUT) break; failed = true; break; } const DWORD wait = WaitForMultipleObjects( _countof(waitHandles), waitHandles, FALSE, INFINITE); if (wait == WAIT_OBJECT_0) { failed = Atomic::Load(m_statusFailed, std::memory_order_acquire); break; } if (wait != WAIT_OBJECT_0 + 1) { DEBUG_ERROR_HR(GetLastError(), "LGMP input worker wait failed"); failed = true; break; } } ReleaseOwner(true); UpdateTargetState({}); PublishStatus(); if (failed && m_target) m_target->Failed(); if (avTaskHandle) AvRevertMmThreadCharacteristics(avTaskHandle); }