[client/idd] network: add recovery, cursor, and control

This commit is contained in:
Geoffrey McRae
2026-09-30 20:55:56 +10:00
parent 6dfba7519c
commit a67034b14f
15 changed files with 3241 additions and 101 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -20,8 +20,46 @@
#include "transport/network/CNetworkSession.h"
#include <new>
#include <utility>
namespace
{
uint64_t NextRevision(uint64_t revision)
{
++revision;
if (!revision)
++revision;
return revision;
}
LGNetControlStatusCode ControlStatusCode(InteractionResult result)
{
switch (result)
{
case InteractionResult::ACCEPTED:
return LG_NET_CONTROL_STATUS_APPLIED;
case InteractionResult::BUSY:
return LG_NET_CONTROL_STATUS_BUSY;
case InteractionResult::UNAVAILABLE:
return LG_NET_CONTROL_STATUS_UNSUPPORTED;
case InteractionResult::STALE:
return LG_NET_CONTROL_STATUS_STALE;
case InteractionResult::REJECTED:
return LG_NET_CONTROL_STATUS_INVALID;
case InteractionResult::FAILED:
return LG_NET_CONTROL_STATUS_ERROR;
}
return LG_NET_CONTROL_STATUS_ERROR;
}
}
bool CNetworkEventQueue::Push(const NetworkEvent& event)
{
CSRWExclusiveLock lock(m_lock);
@@ -147,47 +185,87 @@ ControlResult CNetworkControlSink::SendCursor(
{
const bool shape = info.CursorShapeInfo.CursorType !=
IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED;
if (shape && info.CursorShapeInfo.CursorType !=
IDDCX_CURSOR_SHAPE_TYPE_ALPHA &&
info.CursorShapeInfo.CursorType !=
IDDCX_CURSOR_SHAPE_TYPE_MASKED_COLOR)
return ControlResult::FAILED;
if (shape && info.CursorShapeInfo.Height &&
info.CursorShapeInfo.Pitch >
SIZE_MAX / info.CursorShapeInfo.Height)
return ControlResult::FAILED;
const size_t shapeSize = shape ? static_cast<size_t>(
info.CursorShapeInfo.Height) * info.CursorShapeInfo.Pitch : 0;
if (size != shapeSize || (size && !data))
if (size != shapeSize || (size && !data) ||
shapeSize > LG_NET_CURSOR_MAX_SHAPE_LENGTH ||
(shape && (!info.CursorShapeInfo.Width ||
info.CursorShapeInfo.Width > LG_NET_CURSOR_MAX_WIDTH ||
!info.CursorShapeInfo.Height ||
info.CursorShapeInfo.Height > LG_NET_CURSOR_MAX_HEIGHT ||
info.CursorShapeInfo.XHot >= info.CursorShapeInfo.Width ||
info.CursorShapeInfo.YHot >= info.CursorShapeInfo.Height ||
info.CursorShapeInfo.Pitch < info.CursorShapeInfo.Width * 4U)))
return ControlResult::FAILED;
std::vector<BYTE> cursorData;
if (shapeSize)
cursorData.assign(data, data + size);
try
{
if (shapeSize)
cursorData.assign(data, data + size);
}
catch (...)
{
return ControlResult::FAILED;
}
CSRWExclusiveLock lock(m_lock);
if (!m_events)
return ControlResult::RETRY;
const UINT whiteLevel = sdrWhiteLevel ?
sdrWhiteLevel : LG_SDR_WHITE_LEVEL_DEFAULT;
if (m_state.sdrWhiteLevel != whiteLevel)
{
m_state.sdrWhiteLevel = whiteLevel;
m_state.colorRevision = NextRevision(m_state.colorRevision);
}
m_state.cursor.IsCursorVisible = info.IsCursorVisible;
m_state.cursor.X = info.X;
m_state.cursor.Y = info.Y;
m_state.sdrWhiteLevel = sdrWhiteLevel;
m_state.cursorValid = true;
m_state.positionRevision = NextRevision(m_state.positionRevision);
if (shape)
{
m_state.cursor.IsCursorShapeUpdated = info.IsCursorShapeUpdated;
m_state.cursor.CursorShapeInfo = info.CursorShapeInfo;
m_state.cursorData = std::move(cursorData);
m_state.shapeValid = true;
m_state.shapeRevision = NextRevision(m_state.shapeRevision);
}
++m_state.revision;
m_state.stateRevision = NextRevision(m_state.stateRevision);
return ControlResult::APPLIED;
}
ControlResult CNetworkControlSink::SetColorTransform(
std::shared_ptr<const D12ColorTransform> transform)
{
std::shared_ptr<const D12ColorTransform> copy;
try
{
if (transform)
copy = std::make_shared<D12ColorTransform>(*transform);
}
catch (...)
{
return ControlResult::FAILED;
}
CSRWExclusiveLock lock(m_lock);
if (!m_events)
return ControlResult::RETRY;
m_state.transform = std::move(transform);
m_state.transform = std::move(copy);
m_state.transformValid = true;
++m_state.revision;
m_state.colorRevision = NextRevision(m_state.colorRevision);
m_state.stateRevision = NextRevision(m_state.stateRevision);
return ControlResult::APPLIED;
}
@@ -198,12 +276,21 @@ void CNetworkControlSink::RequestReplay()
m_events->OnControlReplay(m_token);
}
bool CNetworkControlSink::Snapshot(NetworkControlSnapshot& state) const
bool CNetworkControlSink::Snapshot(
NetworkControlSnapshot& state, uint64_t knownRevision) const
{
CSRWSharedLock lock(m_lock);
if (!m_events)
if (!m_events || !m_state.stateRevision ||
m_state.stateRevision == knownRevision)
return false;
state = m_state;
try
{
state = m_state;
}
catch (...)
{
return false;
}
return true;
}
@@ -243,31 +330,50 @@ void CNetworkSession::Close()
m_input.Release(m_source, true);
}
bool CNetworkSession::SetCursorPosition(int32_t x, int32_t y)
bool CNetworkSession::SetCursorPosition(
uint64_t controlID, int32_t x, int32_t y)
{
CSRWSharedLock lock(m_lifecycleLock);
if (!m_open)
if (!m_open || !controlID)
return false;
NetworkEvent event;
event.type = NetworkEventType::SET_CURSOR_POSITION;
event.source = EventSource();
event.x = x;
event.y = y;
event.type = NetworkEventType::SET_CURSOR_POSITION;
event.source = EventSource();
event.controlID = controlID;
event.x = x;
event.y = y;
return m_events.Push(event);
}
bool CNetworkSession::SetResolution(uint32_t width, uint32_t height)
bool CNetworkSession::SetResolution(
uint64_t controlID, uint32_t width, uint32_t height)
{
CSRWSharedLock lock(m_lifecycleLock);
if (!m_open || !width || !height)
if (!m_open || !controlID || !width || !height)
return false;
NetworkEvent event;
event.type = NetworkEventType::SET_RESOLUTION;
event.source = EventSource();
event.width = width;
event.height = height;
event.type = NetworkEventType::SET_RESOLUTION;
event.source = EventSource();
event.controlID = controlID;
event.width = width;
event.height = height;
return m_events.Push(event);
}
bool CNetworkSession::SetFrameSchedule(
uint64_t controlID, const FrameScheduleUpdate& schedule)
{
CSRWSharedLock lock(m_lifecycleLock);
if (!m_open || !controlID)
return false;
NetworkEvent event;
event.type = NetworkEventType::SET_FRAME_SCHEDULE;
event.source = EventSource();
event.schedule = schedule;
event.controlID = controlID;
return m_events.Push(event);
}
@@ -298,12 +404,33 @@ void CNetworkSession::ProcessEvent(
switch (event.type)
{
case NetworkEventType::SET_CURSOR_POSITION:
events.OnSetCursorPos(event.source, event.x, event.y);
{
NetworkControlStatus status;
status.controlID = event.controlID;
status.status = ControlStatusCode(
events.OnSetCursorPos(event.source, event.x, event.y));
SetControlStatus(status);
break;
}
case NetworkEventType::SET_RESOLUTION:
events.OnSetResolution(event.source, event.width, event.height);
{
NetworkControlStatus status;
status.controlID = event.controlID;
status.status = ControlStatusCode(events.OnSetResolution(
event.source, event.width, event.height));
SetControlStatus(status);
break;
}
case NetworkEventType::SET_FRAME_SCHEDULE:
{
NetworkControlStatus status;
status.controlID = event.controlID;
status.status = LG_NET_CONTROL_STATUS_UNSUPPORTED;
SetControlStatus(status);
break;
}
case NetworkEventType::RECOVERY_REQUEST:
{
@@ -356,6 +483,36 @@ bool CNetworkSession::TakeRecoveryStatus(NetworkRecoveryStatus& status)
return true;
}
void CNetworkSession::SetControlStatus(
const NetworkControlStatus& status)
{
CSRWExclusiveLock lock(m_controlLock);
if (m_controlCount == CONTROL_CAPACITY)
{
m_control[m_controlHead] = {};
m_controlHead = (m_controlHead + 1) % CONTROL_CAPACITY;
--m_controlCount;
}
const unsigned tail =
(m_controlHead + m_controlCount) % CONTROL_CAPACITY;
m_control[tail] = status;
++m_controlCount;
}
bool CNetworkSession::TakeControlStatus(NetworkControlStatus& status)
{
CSRWExclusiveLock lock(m_controlLock);
if (!m_controlCount)
return false;
status = m_control[m_controlHead];
m_control[m_controlHead] = {};
m_controlHead = (m_controlHead + 1) % CONTROL_CAPACITY;
--m_controlCount;
return true;
}
InputTargetState CNetworkSession::InputState() const
{
CSRWSharedLock lock(m_lifecycleLock);

View File

@@ -21,10 +21,13 @@
#pragma once
#include "CSRWLock.h"
#include "capture/CFrameScheduler.h"
#include "transport/IControlSink.h"
#include "transport/IInputSource.h"
#include "transport/ITransport.h"
#include <LGProtocol/NetworkServices.h>
#include <stdint.h>
#include <vector>
@@ -32,20 +35,23 @@ enum class NetworkEventType : uint8_t
{
SET_CURSOR_POSITION,
SET_RESOLUTION,
SET_FRAME_SCHEDULE,
RECOVERY_REQUEST,
};
struct NetworkEvent
{
NetworkEventType type = NetworkEventType::SET_CURSOR_POSITION;
SourceKey source;
int32_t x = 0;
int32_t y = 0;
uint32_t width = 0;
uint32_t height = 0;
uint64_t session = 0;
uint32_t serial = 0;
bool active = false;
NetworkEventType type = NetworkEventType::SET_CURSOR_POSITION;
SourceKey source;
FrameScheduleUpdate schedule = {};
uint64_t controlID = 0;
int32_t x = 0;
int32_t y = 0;
uint32_t width = 0;
uint32_t height = 0;
uint64_t session = 0;
uint32_t serial = 0;
bool active = false;
};
struct NetworkRecoveryStatus
@@ -57,13 +63,20 @@ struct NetworkRecoveryStatus
uint32_t error = 0;
};
struct NetworkControlStatus
{
uint64_t controlID = 0;
LGNetControlStatusCode status = LG_NET_CONTROL_STATUS_ERROR;
uint32_t detail = 0;
};
class CNetworkEventQueue
{
private:
static constexpr unsigned CAPACITY = 128;
CSRWLock m_lock;
NetworkEvent m_events[CAPACITY] = {};
NetworkEvent m_events[CAPACITY] = {};
unsigned m_head = 0;
unsigned m_count = 0;
@@ -101,11 +114,14 @@ struct NetworkControlSnapshot
IDARG_OUT_QUERY_HWCURSOR cursor = {};
std::vector<BYTE> cursorData;
std::shared_ptr<const D12ColorTransform> transform;
UINT sdrWhiteLevel = 0;
uint64_t revision = 0;
bool cursorValid = false;
bool shapeValid = false;
bool transformValid = false;
UINT sdrWhiteLevel = 0;
uint64_t stateRevision = 0;
uint64_t positionRevision = 0;
uint64_t shapeRevision = 0;
uint64_t colorRevision = 0;
bool cursorValid = false;
bool shapeValid = false;
bool transformValid = false;
};
class CNetworkControlSink final : public IControlSink
@@ -125,7 +141,8 @@ public:
std::shared_ptr<const D12ColorTransform> transform) override;
void RequestReplay();
bool Snapshot(NetworkControlSnapshot& state) const;
bool Snapshot(
NetworkControlSnapshot& state, uint64_t knownRevision) const;
};
// A session is transport-independent state owned by the eventual QUIC
@@ -135,6 +152,7 @@ class CNetworkSession final
{
private:
static constexpr unsigned RECOVERY_CAPACITY = 16;
static constexpr unsigned CONTROL_CAPACITY = 128;
CNetworkEventQueue& m_events;
CNetworkInputSource& m_input;
@@ -143,10 +161,15 @@ private:
bool m_open = true;
CSRWLock m_recoveryLock;
NetworkRecoveryStatus m_recovery[RECOVERY_CAPACITY] = {};
NetworkRecoveryStatus m_recovery[RECOVERY_CAPACITY] = {};
unsigned m_recoveryHead = 0;
unsigned m_recoveryCount = 0;
CSRWLock m_controlLock;
NetworkControlStatus m_control[CONTROL_CAPACITY] = {};
unsigned m_controlHead = 0;
unsigned m_controlCount = 0;
SourceKey EventSource() const;
public:
@@ -161,12 +184,17 @@ public:
bool IsOpen() const;
void Close();
bool SetCursorPosition(int32_t x, int32_t y);
bool SetResolution(uint32_t width, uint32_t height);
bool SetCursorPosition(uint64_t controlID, int32_t x, int32_t y);
bool SetResolution(
uint64_t controlID, uint32_t width, uint32_t height);
bool SetFrameSchedule(
uint64_t controlID, const FrameScheduleUpdate& schedule);
bool RequestRecovery(uint64_t session, uint32_t serial, bool active);
void ProcessEvent(const NetworkEvent& event, ITransportEvents& events);
void SetRecoveryStatus(const NetworkRecoveryStatus& status);
bool TakeRecoveryStatus(NetworkRecoveryStatus& status);
void SetControlStatus(const NetworkControlStatus& status);
bool TakeControlStatus(NetworkControlStatus& status);
InputTargetState InputState() const;
InputResult ClaimInput();

View File

@@ -241,6 +241,7 @@ bool CNetworkTransport::ServiceEnabled(LGNetService service) const
{
case LG_NET_SERVICE_RECOVERY:
case LG_NET_SERVICE_CURSOR:
case LG_NET_SERVICE_CONTROL:
return (m_config.services & TRANSPORT_SERVICE_CONTROL) != 0;
case LG_NET_SERVICE_INPUT:
@@ -265,9 +266,9 @@ bool CNetworkTransport::ServiceEnabled(LGNetService service) const
}
bool CNetworkTransport::ControlSnapshot(
NetworkControlSnapshot& state) const
NetworkControlSnapshot& state, uint64_t knownRevision) const
{
return m_control.Snapshot(state);
return m_control.Snapshot(state, knownRevision);
}
std::shared_ptr<CNetworkSession> CNetworkTransport::CreateSession(

View File

@@ -94,7 +94,8 @@ public:
const NetworkSettings& Settings() const { return m_settings; }
bool ServiceEnabled(LGNetService service) const;
bool ControlSnapshot(NetworkControlSnapshot& state) const;
bool ControlSnapshot(
NetworkControlSnapshot& state, uint64_t knownRevision) const;
// The QUIC provider calls these only after authenticating a peer. Keeping
// the registry here makes Stop a barrier for session-originated callbacks.