mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-13 07:17:28 +00:00
[idd] network: add transport session foundation
This commit is contained in:
@@ -88,6 +88,9 @@
|
|||||||
<ClCompile Include="transport\lgmp\CLGMPHost.cpp" />
|
<ClCompile Include="transport\lgmp\CLGMPHost.cpp" />
|
||||||
<ClCompile Include="transport\lgmp\CLGMPInputTransport.cpp" />
|
<ClCompile Include="transport\lgmp\CLGMPInputTransport.cpp" />
|
||||||
<ClCompile Include="transport\lgmp\CLGMPTransport.cpp" />
|
<ClCompile Include="transport\lgmp\CLGMPTransport.cpp" />
|
||||||
|
<ClCompile Include="transport\network\CNetworkSession.cpp" />
|
||||||
|
<ClCompile Include="transport\network\CNetworkTransport.cpp" />
|
||||||
|
<ClCompile Include="transport\network\NetworkSettings.cpp" />
|
||||||
<ClCompile Include="config\CSettings.cpp" />
|
<ClCompile Include="config\CSettings.cpp" />
|
||||||
<ClCompile Include="platform\CPlatformInfo.cpp" />
|
<ClCompile Include="platform\CPlatformInfo.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -168,6 +171,9 @@
|
|||||||
<ClInclude Include="transport\lgmp\CLGMPHost.h" />
|
<ClInclude Include="transport\lgmp\CLGMPHost.h" />
|
||||||
<ClInclude Include="transport\lgmp\CLGMPInputTransport.h" />
|
<ClInclude Include="transport\lgmp\CLGMPInputTransport.h" />
|
||||||
<ClInclude Include="transport\lgmp\CLGMPTransport.h" />
|
<ClInclude Include="transport\lgmp\CLGMPTransport.h" />
|
||||||
|
<ClInclude Include="transport\network\CNetworkSession.h" />
|
||||||
|
<ClInclude Include="transport\network\CNetworkTransport.h" />
|
||||||
|
<ClInclude Include="transport\network\NetworkSettings.h" />
|
||||||
<ClInclude Include="config\CSettings.h" />
|
<ClInclude Include="config\CSettings.h" />
|
||||||
<ClInclude Include="platform\CPlatformInfo.h" />
|
<ClInclude Include="platform\CPlatformInfo.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
<Filter Include="Transport\LGMP">
|
<Filter Include="Transport\LGMP">
|
||||||
<UniqueIdentifier>{530EE339-4AD6-4AE7-9A74-09420E73D04B}</UniqueIdentifier>
|
<UniqueIdentifier>{530EE339-4AD6-4AE7-9A74-09420E73D04B}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
|
<Filter Include="Transport\Network">
|
||||||
|
<UniqueIdentifier>{9B627995-4F7D-4F7D-AE88-AD0AA785880D}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
<Filter Include="Configuration">
|
<Filter Include="Configuration">
|
||||||
<UniqueIdentifier>{82810F1C-E51C-4DBD-86B2-1CDE8C16A2A9}</UniqueIdentifier>
|
<UniqueIdentifier>{82810F1C-E51C-4DBD-86B2-1CDE8C16A2A9}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
@@ -283,6 +286,15 @@
|
|||||||
<ClInclude Include="transport\lgmp\CLGMPTransport.h">
|
<ClInclude Include="transport\lgmp\CLGMPTransport.h">
|
||||||
<Filter>Transport\LGMP</Filter>
|
<Filter>Transport\LGMP</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="transport\network\CNetworkSession.h">
|
||||||
|
<Filter>Transport\Network</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="transport\network\CNetworkTransport.h">
|
||||||
|
<Filter>Transport\Network</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="transport\network\NetworkSettings.h">
|
||||||
|
<Filter>Transport\Network</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="config\CSettings.h">
|
<ClInclude Include="config\CSettings.h">
|
||||||
<Filter>Configuration</Filter>
|
<Filter>Configuration</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@@ -459,6 +471,15 @@
|
|||||||
<ClCompile Include="transport\lgmp\CLGMPTransport.cpp">
|
<ClCompile Include="transport\lgmp\CLGMPTransport.cpp">
|
||||||
<Filter>Transport\LGMP</Filter>
|
<Filter>Transport\LGMP</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="transport\network\CNetworkSession.cpp">
|
||||||
|
<Filter>Transport\Network</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="transport\network\CNetworkTransport.cpp">
|
||||||
|
<Filter>Transport\Network</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="transport\network\NetworkSettings.cpp">
|
||||||
|
<Filter>Transport\Network</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="config\CSettings.cpp">
|
<ClCompile Include="config\CSettings.cpp">
|
||||||
<Filter>Configuration</Filter>
|
<Filter>Configuration</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
@@ -649,7 +649,7 @@ bool CDeviceContext::SetupTransport(size_t alignSize)
|
|||||||
{
|
{
|
||||||
// Frame buffers cannot be allocated until the GPU-specific alignment is
|
// Frame buffers cannot be allocated until the GPU-specific alignment is
|
||||||
// known. The swap-chain path may call this again after setup completed.
|
// known. The swap-chain path may call this again after setup completed.
|
||||||
if (!m_transport->Frames().GetMaxFrameSize())
|
if (!m_transport->SetupReady())
|
||||||
{
|
{
|
||||||
if (!InitializeTransport() || !m_transport->Setup(alignSize))
|
if (!InitializeTransport() || !m_transport->Setup(alignSize))
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1256,6 +1256,44 @@ bool CTransportManager::Setup(size_t alignment)
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CTransportManager::SetupReady() const
|
||||||
|
{
|
||||||
|
CTransportManager * manager = const_cast<CTransportManager *>(this);
|
||||||
|
if (!manager->BeginPhase(Phase::ACCESS, true))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Entry * entries[FRAME_MAX_SINKS] = {};
|
||||||
|
Entry * primary = nullptr;
|
||||||
|
unsigned count = 0;
|
||||||
|
bool setup = false;
|
||||||
|
{
|
||||||
|
CSRWSharedLock managerLock(m_lock);
|
||||||
|
primary = m_primary;
|
||||||
|
setup = m_setup && !m_stopping && !m_stopped;
|
||||||
|
count = m_entryCount;
|
||||||
|
for (unsigned i = 0; i < count; ++i)
|
||||||
|
entries[i] = m_entries[i].get();
|
||||||
|
}
|
||||||
|
bool primaryReady = false;
|
||||||
|
bool ready = setup && primary != nullptr;
|
||||||
|
for (unsigned i = 0; ready && i < count; ++i)
|
||||||
|
{
|
||||||
|
Entry& entry = *entries[i];
|
||||||
|
CSRWSharedLock entryLock(entry.lock);
|
||||||
|
if (entry.required &&
|
||||||
|
(entry.state != State::READY || !entry.setupDone))
|
||||||
|
{
|
||||||
|
ready = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (&entry == primary)
|
||||||
|
primaryReady = entry.state == State::READY &&
|
||||||
|
entry.setupDone && entry.frameAdded;
|
||||||
|
}
|
||||||
|
manager->EndPhase();
|
||||||
|
return ready && primaryReady;
|
||||||
|
}
|
||||||
|
|
||||||
CfgResult CTransportManager::Cfg(const GraphCfg& cfg,
|
CfgResult CTransportManager::Cfg(const GraphCfg& cfg,
|
||||||
uint64_t revision, CFrameGraph& graph, ITexStage * activation)
|
uint64_t revision, CFrameGraph& graph, ITexStage * activation)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -195,6 +195,9 @@ public:
|
|||||||
OpenResult Open();
|
OpenResult Open();
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
bool Setup(size_t alignment);
|
bool Setup(size_t alignment);
|
||||||
|
// Unlike the legacy frame-size sentinel, this also recognizes a primary
|
||||||
|
// texture route and verifies every required transport completed setup.
|
||||||
|
bool SetupReady() const;
|
||||||
CfgResult Cfg(const GraphCfg& cfg, uint64_t revision,
|
CfgResult Cfg(const GraphCfg& cfg, uint64_t revision,
|
||||||
CFrameGraph& graph, ITexStage * activation = nullptr);
|
CFrameGraph& graph, ITexStage * activation = nullptr);
|
||||||
ProcessResult Process(ITransportActions& actions);
|
ProcessResult Process(ITransportActions& actions);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "config/CSettings.h"
|
#include "config/CSettings.h"
|
||||||
#include "transport/CTransportManager.h"
|
#include "transport/CTransportManager.h"
|
||||||
#include "transport/lgmp/CLGMPTransport.h"
|
#include "transport/lgmp/CLGMPTransport.h"
|
||||||
|
#include "transport/network/CNetworkTransport.h"
|
||||||
|
|
||||||
#include <new>
|
#include <new>
|
||||||
|
|
||||||
@@ -41,14 +42,23 @@ namespace
|
|||||||
new (std::nothrow) CLGMPTransport(config));
|
new (std::nothrow) CLGMPTransport(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ITransport> CreateNetwork(
|
||||||
|
const TransportInstance& config)
|
||||||
|
{
|
||||||
|
return std::unique_ptr<ITransport>(
|
||||||
|
new (std::nothrow) CNetworkTransport(config));
|
||||||
|
}
|
||||||
|
|
||||||
const TransportKind KINDS[] =
|
const TransportKind KINDS[] =
|
||||||
{
|
{
|
||||||
{ L"LGMP", 1 },
|
{ L"LGMP", 1 },
|
||||||
|
{ L"NETWORK", 1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const Provider PROVIDERS[] =
|
const Provider PROVIDERS[] =
|
||||||
{
|
{
|
||||||
{ CreateLGMP },
|
{ CreateLGMP },
|
||||||
|
{ CreateNetwork },
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(ARRAYSIZE(KINDS) == ARRAYSIZE(PROVIDERS),
|
static_assert(ARRAYSIZE(KINDS) == ARRAYSIZE(PROVIDERS),
|
||||||
|
|||||||
415
idd/LGIdd/transport/network/CNetworkSession.cpp
Normal file
415
idd/LGIdd/transport/network/CNetworkSession.cpp
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
/**
|
||||||
|
* 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/network/CNetworkSession.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
bool CNetworkEventQueue::Push(const NetworkEvent& event)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (m_count == CAPACITY)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const unsigned tail = (m_head + m_count) % CAPACITY;
|
||||||
|
m_events[tail] = event;
|
||||||
|
++m_count;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkEventQueue::Take(NetworkEvent& event)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (!m_count)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
event = m_events[m_head];
|
||||||
|
m_events[m_head] = {};
|
||||||
|
m_head = (m_head + 1) % CAPACITY;
|
||||||
|
--m_count;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkEventQueue::Reset()
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
for (NetworkEvent& event : m_events)
|
||||||
|
event = {};
|
||||||
|
m_head = 0;
|
||||||
|
m_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkInputSource::Start(IInputTarget& target)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (m_target && m_target != &target)
|
||||||
|
return false;
|
||||||
|
m_target = ⌖
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkInputSource::Stop()
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
m_target = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputTargetState CNetworkInputSource::GetState(
|
||||||
|
const InputSourceId& source) const
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
return m_target ? m_target->GetState(source) : InputTargetState {};
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkInputSource::Claim(const InputSourceId& source)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
return m_target ?
|
||||||
|
m_target->Claim(source) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkInputSource::Touch(const InputSourceId& source)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
return m_target ?
|
||||||
|
m_target->Touch(source) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkInputSource::Release(
|
||||||
|
const InputSourceId& source, bool reset)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
return m_target ?
|
||||||
|
m_target->Release(source, reset) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkInputSource::SendMouseRelative(
|
||||||
|
const InputSourceId& source, int32_t deltaX, int32_t deltaY,
|
||||||
|
int32_t wheel, uint32_t buttons)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
return m_target ? m_target->SendMouseRelative(
|
||||||
|
source, deltaX, deltaY, wheel, buttons) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkInputSource::SendMouseAbsolute(
|
||||||
|
const InputSourceId& source, uint16_t x, uint16_t y,
|
||||||
|
int32_t wheel, uint32_t buttons)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
return m_target ? m_target->SendMouseAbsolute(
|
||||||
|
source, x, y, wheel, buttons) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkInputSource::SendKeyboard(
|
||||||
|
const InputSourceId& source, uint8_t modifiers, const uint8_t * keys)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
return m_target ? m_target->SendKeyboard(
|
||||||
|
source, modifiers, keys) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkInputSource::Reset(const InputSourceId& source)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
return m_target ?
|
||||||
|
m_target->Reset(source) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkControlSink::SetControlEvents(
|
||||||
|
IControlEvents * events, const ControlToken& token)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
m_events = events;
|
||||||
|
m_token = events ? token : ControlToken {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlResult CNetworkControlSink::SendCursor(
|
||||||
|
const IDARG_OUT_QUERY_HWCURSOR& info, const BYTE * data,
|
||||||
|
size_t size, UINT sdrWhiteLevel)
|
||||||
|
{
|
||||||
|
const bool shape = info.CursorShapeInfo.CursorType !=
|
||||||
|
IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED;
|
||||||
|
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))
|
||||||
|
return ControlResult::FAILED;
|
||||||
|
|
||||||
|
std::vector<BYTE> cursorData;
|
||||||
|
if (shapeSize)
|
||||||
|
cursorData.assign(data, data + size);
|
||||||
|
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (!m_events)
|
||||||
|
return ControlResult::RETRY;
|
||||||
|
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;
|
||||||
|
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.revision;
|
||||||
|
return ControlResult::APPLIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlResult CNetworkControlSink::SetColorTransform(
|
||||||
|
std::shared_ptr<const D12ColorTransform> transform)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (!m_events)
|
||||||
|
return ControlResult::RETRY;
|
||||||
|
m_state.transform = std::move(transform);
|
||||||
|
m_state.transformValid = true;
|
||||||
|
++m_state.revision;
|
||||||
|
return ControlResult::APPLIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkControlSink::RequestReplay()
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
if (m_events)
|
||||||
|
m_events->OnControlReplay(m_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkControlSink::Snapshot(NetworkControlSnapshot& state) const
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
if (!m_events)
|
||||||
|
return false;
|
||||||
|
state = m_state;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CNetworkSession::CNetworkSession(uint32_t client, uint32_t generation,
|
||||||
|
CNetworkEventQueue& events, CNetworkInputSource& input) :
|
||||||
|
m_events(events), m_input(input)
|
||||||
|
{
|
||||||
|
m_source.client = client;
|
||||||
|
m_source.generation = generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
CNetworkSession::~CNetworkSession()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceKey CNetworkSession::EventSource() const
|
||||||
|
{
|
||||||
|
SourceKey source;
|
||||||
|
source.client = m_source.client;
|
||||||
|
source.generation = m_source.generation;
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkSession::IsOpen() const
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
return m_open;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkSession::Close()
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||||
|
if (!m_open)
|
||||||
|
return;
|
||||||
|
m_open = false;
|
||||||
|
m_input.Release(m_source, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkSession::SetCursorPosition(int32_t x, int32_t y)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
if (!m_open)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NetworkEvent event;
|
||||||
|
event.type = NetworkEventType::SET_CURSOR_POSITION;
|
||||||
|
event.source = EventSource();
|
||||||
|
event.x = x;
|
||||||
|
event.y = y;
|
||||||
|
return m_events.Push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkSession::SetResolution(uint32_t width, uint32_t height)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
if (!m_open || !width || !height)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NetworkEvent event;
|
||||||
|
event.type = NetworkEventType::SET_RESOLUTION;
|
||||||
|
event.source = EventSource();
|
||||||
|
event.width = width;
|
||||||
|
event.height = height;
|
||||||
|
return m_events.Push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkSession::RequestRecovery(
|
||||||
|
uint64_t session, uint32_t serial, bool active)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
if (!m_open || !session || !serial)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NetworkEvent event;
|
||||||
|
event.type = NetworkEventType::RECOVERY_REQUEST;
|
||||||
|
event.source = EventSource();
|
||||||
|
event.session = session;
|
||||||
|
event.serial = serial;
|
||||||
|
event.active = active;
|
||||||
|
return m_events.Push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkSession::ProcessEvent(
|
||||||
|
const NetworkEvent& event, ITransportEvents& events)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
if (!m_open || event.source.client != m_source.client ||
|
||||||
|
event.source.generation != m_source.generation)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (event.type)
|
||||||
|
{
|
||||||
|
case NetworkEventType::SET_CURSOR_POSITION:
|
||||||
|
events.OnSetCursorPos(event.source, event.x, event.y);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NetworkEventType::SET_RESOLUTION:
|
||||||
|
events.OnSetResolution(event.source, event.width, event.height);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NetworkEventType::RECOVERY_REQUEST:
|
||||||
|
{
|
||||||
|
const RecoveryAdmission admission = events.OnRecoveryRequest(
|
||||||
|
event.source, event.session, event.serial, event.active);
|
||||||
|
if (!admission.complete)
|
||||||
|
break;
|
||||||
|
|
||||||
|
NetworkRecoveryStatus status;
|
||||||
|
status.session = event.session;
|
||||||
|
status.serial = event.serial;
|
||||||
|
status.active = event.active;
|
||||||
|
status.state = admission.state;
|
||||||
|
status.error = admission.error;
|
||||||
|
SetRecoveryStatus(status);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkSession::SetRecoveryStatus(
|
||||||
|
const NetworkRecoveryStatus& status)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_recoveryLock);
|
||||||
|
if (m_recoveryCount == RECOVERY_CAPACITY)
|
||||||
|
{
|
||||||
|
m_recovery[m_recoveryHead] = {};
|
||||||
|
m_recoveryHead =
|
||||||
|
(m_recoveryHead + 1) % RECOVERY_CAPACITY;
|
||||||
|
--m_recoveryCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned tail =
|
||||||
|
(m_recoveryHead + m_recoveryCount) % RECOVERY_CAPACITY;
|
||||||
|
m_recovery[tail] = status;
|
||||||
|
++m_recoveryCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkSession::TakeRecoveryStatus(NetworkRecoveryStatus& status)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_recoveryLock);
|
||||||
|
if (!m_recoveryCount)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
status = m_recovery[m_recoveryHead];
|
||||||
|
m_recovery[m_recoveryHead] = {};
|
||||||
|
m_recoveryHead =
|
||||||
|
(m_recoveryHead + 1) % RECOVERY_CAPACITY;
|
||||||
|
--m_recoveryCount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputTargetState CNetworkSession::InputState() const
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
return m_open ? m_input.GetState(m_source) : InputTargetState {};
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkSession::ClaimInput()
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
return m_open ?
|
||||||
|
m_input.Claim(m_source) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkSession::TouchInput()
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
return m_open ?
|
||||||
|
m_input.Touch(m_source) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkSession::ReleaseInput(bool reset)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
return m_open ?
|
||||||
|
m_input.Release(m_source, reset) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkSession::SendMouseRelative(
|
||||||
|
int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
return m_open ? m_input.SendMouseRelative(
|
||||||
|
m_source, deltaX, deltaY, wheel, buttons) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkSession::SendMouseAbsolute(
|
||||||
|
uint16_t x, uint16_t y, int32_t wheel, uint32_t buttons)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
return m_open ? m_input.SendMouseAbsolute(
|
||||||
|
m_source, x, y, wheel, buttons) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkSession::SendKeyboard(
|
||||||
|
uint8_t modifiers, const uint8_t * keys)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
return m_open ? m_input.SendKeyboard(
|
||||||
|
m_source, modifiers, keys) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult CNetworkSession::ResetInput()
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
return m_open ?
|
||||||
|
m_input.Reset(m_source) : InputResult::UNAVAILABLE;
|
||||||
|
}
|
||||||
181
idd/LGIdd/transport/network/CNetworkSession.h
Normal file
181
idd/LGIdd/transport/network/CNetworkSession.h
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CSRWLock.h"
|
||||||
|
#include "transport/IControlSink.h"
|
||||||
|
#include "transport/IInputSource.h"
|
||||||
|
#include "transport/ITransport.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
enum class NetworkEventType : uint8_t
|
||||||
|
{
|
||||||
|
SET_CURSOR_POSITION,
|
||||||
|
SET_RESOLUTION,
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NetworkRecoveryStatus
|
||||||
|
{
|
||||||
|
uint64_t session = 0;
|
||||||
|
uint32_t serial = 0;
|
||||||
|
bool active = false;
|
||||||
|
RecoveryState state = RecoveryState::FAILED;
|
||||||
|
uint32_t error = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CNetworkEventQueue
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static constexpr unsigned CAPACITY = 128;
|
||||||
|
|
||||||
|
CSRWLock m_lock;
|
||||||
|
NetworkEvent m_events[CAPACITY] = {};
|
||||||
|
unsigned m_head = 0;
|
||||||
|
unsigned m_count = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool Push(const NetworkEvent& event);
|
||||||
|
bool Take(NetworkEvent& event);
|
||||||
|
void Reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
class CNetworkInputSource final : public IInputSource
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
mutable CSRWLock m_lock;
|
||||||
|
IInputTarget * m_target = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool Start(IInputTarget& target) override;
|
||||||
|
void Stop() override;
|
||||||
|
|
||||||
|
InputTargetState GetState(const InputSourceId& source) const;
|
||||||
|
InputResult Claim(const InputSourceId& source);
|
||||||
|
InputResult Touch(const InputSourceId& source);
|
||||||
|
InputResult Release(const InputSourceId& source, bool reset);
|
||||||
|
InputResult SendMouseRelative(const InputSourceId& source,
|
||||||
|
int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons);
|
||||||
|
InputResult SendMouseAbsolute(const InputSourceId& source,
|
||||||
|
uint16_t x, uint16_t y, int32_t wheel, uint32_t buttons);
|
||||||
|
InputResult SendKeyboard(const InputSourceId& source,
|
||||||
|
uint8_t modifiers, const uint8_t * keys);
|
||||||
|
InputResult Reset(const InputSourceId& source);
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CNetworkControlSink final : public IControlSink
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
mutable CSRWLock m_lock;
|
||||||
|
IControlEvents * m_events = nullptr;
|
||||||
|
ControlToken m_token;
|
||||||
|
NetworkControlSnapshot m_state;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void SetControlEvents(
|
||||||
|
IControlEvents * events, const ControlToken& token) override;
|
||||||
|
ControlResult SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info,
|
||||||
|
const BYTE * data, size_t size, UINT sdrWhiteLevel) override;
|
||||||
|
ControlResult SetColorTransform(
|
||||||
|
std::shared_ptr<const D12ColorTransform> transform) override;
|
||||||
|
|
||||||
|
void RequestReplay();
|
||||||
|
bool Snapshot(NetworkControlSnapshot& state) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A session is transport-independent state owned by the eventual QUIC
|
||||||
|
// provider. Decoded requests enter the bounded event queue or the existing
|
||||||
|
// input hub; encoded replies are drained from the bounded status queue.
|
||||||
|
class CNetworkSession final
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static constexpr unsigned RECOVERY_CAPACITY = 16;
|
||||||
|
|
||||||
|
CNetworkEventQueue& m_events;
|
||||||
|
CNetworkInputSource& m_input;
|
||||||
|
InputSourceId m_source;
|
||||||
|
mutable CSRWLock m_lifecycleLock;
|
||||||
|
bool m_open = true;
|
||||||
|
|
||||||
|
CSRWLock m_recoveryLock;
|
||||||
|
NetworkRecoveryStatus m_recovery[RECOVERY_CAPACITY] = {};
|
||||||
|
unsigned m_recoveryHead = 0;
|
||||||
|
unsigned m_recoveryCount = 0;
|
||||||
|
|
||||||
|
SourceKey EventSource() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CNetworkSession(uint32_t client, uint32_t generation,
|
||||||
|
CNetworkEventQueue& events, CNetworkInputSource& input);
|
||||||
|
~CNetworkSession();
|
||||||
|
|
||||||
|
CNetworkSession(const CNetworkSession&) = delete;
|
||||||
|
CNetworkSession& operator=(const CNetworkSession&) = delete;
|
||||||
|
|
||||||
|
InputSourceId Source() const { return m_source; }
|
||||||
|
bool IsOpen() const;
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
bool SetCursorPosition(int32_t x, int32_t y);
|
||||||
|
bool SetResolution(uint32_t width, uint32_t height);
|
||||||
|
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);
|
||||||
|
|
||||||
|
InputTargetState InputState() const;
|
||||||
|
InputResult ClaimInput();
|
||||||
|
InputResult TouchInput();
|
||||||
|
InputResult ReleaseInput(bool reset);
|
||||||
|
InputResult SendMouseRelative(
|
||||||
|
int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons);
|
||||||
|
InputResult SendMouseAbsolute(
|
||||||
|
uint16_t x, uint16_t y, int32_t wheel, uint32_t buttons);
|
||||||
|
InputResult SendKeyboard(uint8_t modifiers, const uint8_t * keys);
|
||||||
|
InputResult ResetInput();
|
||||||
|
};
|
||||||
273
idd/LGIdd/transport/network/CNetworkTransport.cpp
Normal file
273
idd/LGIdd/transport/network/CNetworkTransport.cpp
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
/**
|
||||||
|
* 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/network/CNetworkTransport.h"
|
||||||
|
|
||||||
|
#include "CDebug.h"
|
||||||
|
|
||||||
|
#include <new>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
static constexpr unsigned EVENT_DRAIN_LIMIT = 64;
|
||||||
|
|
||||||
|
CNetworkTransport::CNetworkTransport(const TransportInstance& config) :
|
||||||
|
m_config(config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CNetworkTransport::~CNetworkTransport()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ITransport::OpenResult CNetworkTransport::Open()
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||||
|
if (m_state == State::STOPPED)
|
||||||
|
return OpenResult::FAILURE;
|
||||||
|
if (m_state != State::CONSTRUCTED)
|
||||||
|
return OpenResult::SUCCESS;
|
||||||
|
|
||||||
|
NetworkSettings settings;
|
||||||
|
std::wstring error;
|
||||||
|
if (!ParseNetworkSettings(m_config.settings, settings, &error))
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("Invalid network transport settings: %ls", error.c_str());
|
||||||
|
return OpenResult::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_settings = std::move(settings);
|
||||||
|
m_state = State::OPEN;
|
||||||
|
DEBUG_INFO("Network transport configured on [%ls]:%u for up to %u "
|
||||||
|
"clients (%ls authentication)",
|
||||||
|
m_settings.bindAddress.c_str(),
|
||||||
|
static_cast<unsigned>(m_settings.port),
|
||||||
|
static_cast<unsigned>(m_settings.maxClients),
|
||||||
|
m_settings.auth == LG_NET_AUTH_PASSWORD ? L"password" : L"none");
|
||||||
|
return OpenResult::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkTransport::Initialize()
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||||
|
if (m_state == State::INITIALIZED || m_state == State::READY)
|
||||||
|
return true;
|
||||||
|
if (m_state != State::OPEN)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_state = State::INITIALIZED;
|
||||||
|
if (m_config.services & TRANSPORT_SERVICE_FRAME)
|
||||||
|
DEBUG_WARN("Network video is unavailable until the GPU codec and "
|
||||||
|
"texture sink are attached");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkTransport::Setup(size_t alignment)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||||
|
if (m_state == State::READY)
|
||||||
|
return m_alignment == alignment;
|
||||||
|
if (m_state != State::INITIALIZED)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_alignment = alignment;
|
||||||
|
m_state = State::READY;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ITransport::ProcessResult CNetworkTransport::Process(
|
||||||
|
ITransportEvents& events)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lifecycleLock);
|
||||||
|
if (m_state != State::INITIALIZED && m_state != State::READY)
|
||||||
|
return m_state == State::STOPPED ?
|
||||||
|
ProcessResult::FAILURE : ProcessResult::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkEvent event;
|
||||||
|
for (unsigned i = 0; i < EVENT_DRAIN_LIMIT && m_events.Take(event); ++i)
|
||||||
|
{
|
||||||
|
const SessionPtr session = FindSession(event.source);
|
||||||
|
if (!session)
|
||||||
|
continue;
|
||||||
|
session->ProcessEvent(event, events);
|
||||||
|
}
|
||||||
|
return ProcessResult::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkTransport::Stop()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||||
|
if (m_state == State::STOPPED)
|
||||||
|
return;
|
||||||
|
m_state = State::STOPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SessionPtr> sessions;
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_sessionLock);
|
||||||
|
sessions.swap(m_sessions);
|
||||||
|
}
|
||||||
|
for (const SessionPtr& session : sessions)
|
||||||
|
session->Close();
|
||||||
|
|
||||||
|
m_input.Stop();
|
||||||
|
m_control.SetControlEvents(nullptr, {});
|
||||||
|
m_events.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkTransport::RecoveryStatus(const SourceKey& source,
|
||||||
|
uint64_t session, uint32_t serial, bool active,
|
||||||
|
Recovery state, uint32_t error)
|
||||||
|
{
|
||||||
|
const SessionPtr target = FindSession(source);
|
||||||
|
if (!target)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NetworkRecoveryStatus status;
|
||||||
|
status.session = session;
|
||||||
|
status.serial = serial;
|
||||||
|
status.active = active;
|
||||||
|
status.state = state;
|
||||||
|
status.error = error;
|
||||||
|
target->SetRecoveryStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FrameProfile * CNetworkTransport::Profiles(unsigned& count) const
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CfgResult CNetworkTransport::Probe(const FrameCfg& cfg) const
|
||||||
|
{
|
||||||
|
UNREFERENCED_PARAMETER(cfg);
|
||||||
|
return CfgResult::REJECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
CfgResult CNetworkTransport::Prepare(const FrameCfg& cfg)
|
||||||
|
{
|
||||||
|
UNREFERENCED_PARAMETER(cfg);
|
||||||
|
return CfgResult::REJECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkTransport::Commit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkTransport::Abort()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const FrameCaps> CNetworkTransport::GetFrameCaps() const
|
||||||
|
{
|
||||||
|
return std::shared_ptr<const FrameCaps>();
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectFrameBufferMemory CNetworkTransport::GetDirectMemory() const
|
||||||
|
{
|
||||||
|
return DirectFrameBufferMemory {};
|
||||||
|
}
|
||||||
|
|
||||||
|
IControlSink * CNetworkTransport::Control()
|
||||||
|
{
|
||||||
|
return (m_config.services & TRANSPORT_SERVICE_CONTROL) ?
|
||||||
|
&m_control : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
IInputSource * CNetworkTransport::Input()
|
||||||
|
{
|
||||||
|
return (m_config.services & TRANSPORT_SERVICE_INPUT) ? &m_input : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNetworkTransport::ControlSnapshot(
|
||||||
|
NetworkControlSnapshot& state) const
|
||||||
|
{
|
||||||
|
return m_control.Snapshot(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CNetworkSession> CNetworkTransport::CreateSession(
|
||||||
|
uint32_t client, uint32_t generation)
|
||||||
|
{
|
||||||
|
if (!client || !generation)
|
||||||
|
return std::shared_ptr<CNetworkSession>();
|
||||||
|
|
||||||
|
SessionPtr created;
|
||||||
|
{
|
||||||
|
CSRWSharedLock lifecycleLock(m_lifecycleLock);
|
||||||
|
if (m_state != State::READY)
|
||||||
|
return std::shared_ptr<CNetworkSession>();
|
||||||
|
|
||||||
|
CSRWExclusiveLock lock(m_sessionLock);
|
||||||
|
if (m_sessions.size() >= m_settings.maxClients)
|
||||||
|
return std::shared_ptr<CNetworkSession>();
|
||||||
|
for (const SessionPtr& existing : m_sessions)
|
||||||
|
{
|
||||||
|
const InputSourceId source = existing->Source();
|
||||||
|
if (source.client == client && source.generation == generation)
|
||||||
|
return std::shared_ptr<CNetworkSession>();
|
||||||
|
}
|
||||||
|
|
||||||
|
created.reset(
|
||||||
|
new (std::nothrow) CNetworkSession(
|
||||||
|
client, generation, m_events, m_input));
|
||||||
|
if (!created)
|
||||||
|
return std::shared_ptr<CNetworkSession>();
|
||||||
|
m_sessions.push_back(created);
|
||||||
|
}
|
||||||
|
m_control.RequestReplay();
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNetworkTransport::RemoveSession(
|
||||||
|
uint32_t client, uint32_t generation)
|
||||||
|
{
|
||||||
|
SessionPtr removed;
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_sessionLock);
|
||||||
|
for (auto it = m_sessions.begin(); it != m_sessions.end(); ++it)
|
||||||
|
{
|
||||||
|
const InputSourceId source = (*it)->Source();
|
||||||
|
if (source.client != client || source.generation != generation)
|
||||||
|
continue;
|
||||||
|
removed = *it;
|
||||||
|
m_sessions.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removed)
|
||||||
|
removed->Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CNetworkSession> CNetworkTransport::FindSession(
|
||||||
|
const SourceKey& source) const
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_sessionLock);
|
||||||
|
for (const SessionPtr& session : m_sessions)
|
||||||
|
{
|
||||||
|
const InputSourceId candidate = session->Source();
|
||||||
|
if (candidate.client == source.client &&
|
||||||
|
candidate.generation == source.generation)
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
return std::shared_ptr<CNetworkSession>();
|
||||||
|
}
|
||||||
100
idd/LGIdd/transport/network/CNetworkTransport.h
Normal file
100
idd/LGIdd/transport/network/CNetworkTransport.h
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CSRWLock.h"
|
||||||
|
#include "transport/ITransport.h"
|
||||||
|
#include "transport/network/CNetworkSession.h"
|
||||||
|
#include "transport/network/NetworkSettings.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// This provider owns transport-independent lifecycle and session state. It
|
||||||
|
// deliberately advertises no frame capabilities and no texture sink until a
|
||||||
|
// GPU codec backend is attached; it must not be mistaken for working media.
|
||||||
|
class CNetworkTransport final : public ITransport
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using SessionPtr = std::shared_ptr<CNetworkSession>;
|
||||||
|
|
||||||
|
enum class State
|
||||||
|
{
|
||||||
|
CONSTRUCTED,
|
||||||
|
OPEN,
|
||||||
|
INITIALIZED,
|
||||||
|
READY,
|
||||||
|
STOPPED,
|
||||||
|
};
|
||||||
|
|
||||||
|
TransportInstance m_config;
|
||||||
|
NetworkSettings m_settings;
|
||||||
|
|
||||||
|
mutable CSRWLock m_lifecycleLock;
|
||||||
|
State m_state = State::CONSTRUCTED;
|
||||||
|
size_t m_alignment = 0;
|
||||||
|
|
||||||
|
CNetworkEventQueue m_events;
|
||||||
|
CNetworkInputSource m_input;
|
||||||
|
CNetworkControlSink m_control;
|
||||||
|
|
||||||
|
mutable CSRWLock m_sessionLock;
|
||||||
|
std::vector<SessionPtr> m_sessions;
|
||||||
|
|
||||||
|
std::shared_ptr<CNetworkSession> FindSession(
|
||||||
|
const SourceKey& source) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CNetworkTransport(const TransportInstance& config);
|
||||||
|
~CNetworkTransport() override;
|
||||||
|
|
||||||
|
CNetworkTransport(const CNetworkTransport&) = delete;
|
||||||
|
CNetworkTransport& operator=(const CNetworkTransport&) = delete;
|
||||||
|
|
||||||
|
OpenResult Open() override;
|
||||||
|
bool Initialize() override;
|
||||||
|
bool Setup(size_t alignment) override;
|
||||||
|
ProcessResult Process(ITransportEvents& events) override;
|
||||||
|
void Stop() override;
|
||||||
|
void RecoveryStatus(const SourceKey& source,
|
||||||
|
uint64_t session, uint32_t serial, bool active,
|
||||||
|
Recovery state, uint32_t error) override;
|
||||||
|
|
||||||
|
const FrameProfile * Profiles(unsigned& count) const override;
|
||||||
|
CfgResult Probe(const FrameCfg& cfg) const override;
|
||||||
|
CfgResult Prepare(const FrameCfg& cfg) override;
|
||||||
|
void Commit() override;
|
||||||
|
void Abort() override;
|
||||||
|
|
||||||
|
std::shared_ptr<const FrameCaps> GetFrameCaps() const override;
|
||||||
|
DirectFrameBufferMemory GetDirectMemory() const override;
|
||||||
|
IControlSink * Control() override;
|
||||||
|
IInputSource * Input() override;
|
||||||
|
|
||||||
|
const NetworkSettings& Settings() const { return m_settings; }
|
||||||
|
bool ControlSnapshot(NetworkControlSnapshot& state) const;
|
||||||
|
|
||||||
|
// The QUIC provider calls these only after authenticating a peer. Keeping
|
||||||
|
// the registry here makes Stop a barrier for session-originated callbacks.
|
||||||
|
std::shared_ptr<CNetworkSession> CreateSession(
|
||||||
|
uint32_t client, uint32_t generation);
|
||||||
|
void RemoveSession(uint32_t client, uint32_t generation);
|
||||||
|
};
|
||||||
194
idd/LGIdd/transport/network/NetworkSettings.cpp
Normal file
194
idd/LGIdd/transport/network/NetworkSettings.cpp
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
* 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/network/NetworkSettings.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <utility>
|
||||||
|
#include <wctype.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
enum Field : unsigned
|
||||||
|
{
|
||||||
|
FIELD_BIND = 1U << 0,
|
||||||
|
FIELD_PORT = 1U << 1,
|
||||||
|
FIELD_MAX_CLIENTS = 1U << 2,
|
||||||
|
FIELD_AUTH = 1U << 3,
|
||||||
|
FIELD_PASSWORD = 1U << 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::wstring Trim(const std::wstring& value)
|
||||||
|
{
|
||||||
|
size_t begin = 0;
|
||||||
|
size_t end = value.size();
|
||||||
|
while (begin < end && iswspace(value[begin]))
|
||||||
|
++begin;
|
||||||
|
while (end > begin && iswspace(value[end - 1]))
|
||||||
|
--end;
|
||||||
|
return value.substr(begin, end - begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Equal(const std::wstring& left, const wchar_t * right)
|
||||||
|
{
|
||||||
|
if (!right)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
for (; index < left.size() && right[index]; ++index)
|
||||||
|
if (towlower(left[index]) != towlower(right[index]))
|
||||||
|
return false;
|
||||||
|
return index == left.size() && !right[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Fail(std::wstring * error, const wchar_t * message)
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
*error = message;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ValidText(const std::wstring& value, size_t maximum)
|
||||||
|
{
|
||||||
|
if (value.empty() || value.size() > maximum)
|
||||||
|
return false;
|
||||||
|
for (wchar_t character : value)
|
||||||
|
if (iswcntrl(character))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParseUnsigned(const std::wstring& value,
|
||||||
|
unsigned long maximum, unsigned long& result)
|
||||||
|
{
|
||||||
|
if (value.empty() || value[0] == L'-')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wchar_t * end = nullptr;
|
||||||
|
errno = 0;
|
||||||
|
const unsigned long parsed = wcstoul(value.c_str(), &end, 10);
|
||||||
|
if (errno == ERANGE || !end || *end || !parsed || parsed > maximum)
|
||||||
|
return false;
|
||||||
|
result = parsed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetField(const std::wstring& key, const std::wstring& rawValue,
|
||||||
|
NetworkSettings& settings, unsigned& fields,
|
||||||
|
std::wstring * error)
|
||||||
|
{
|
||||||
|
const std::wstring value = Trim(rawValue);
|
||||||
|
unsigned field = 0;
|
||||||
|
if (Equal(key, L"bind"))
|
||||||
|
{
|
||||||
|
field = FIELD_BIND;
|
||||||
|
if (!ValidText(value, 255))
|
||||||
|
return Fail(error, L"invalid bind address");
|
||||||
|
settings.bindAddress = value;
|
||||||
|
}
|
||||||
|
else if (Equal(key, L"port"))
|
||||||
|
{
|
||||||
|
field = FIELD_PORT;
|
||||||
|
unsigned long parsed = 0;
|
||||||
|
if (!ParseUnsigned(value, UINT16_MAX, parsed))
|
||||||
|
return Fail(error, L"invalid port");
|
||||||
|
settings.port = static_cast<uint16_t>(parsed);
|
||||||
|
}
|
||||||
|
else if (Equal(key, L"maxClients"))
|
||||||
|
{
|
||||||
|
field = FIELD_MAX_CLIENTS;
|
||||||
|
unsigned long parsed = 0;
|
||||||
|
if (!ParseUnsigned(value, NETWORK_MAX_CLIENTS, parsed))
|
||||||
|
return Fail(error, L"invalid maximum client count");
|
||||||
|
settings.maxClients = static_cast<uint16_t>(parsed);
|
||||||
|
}
|
||||||
|
else if (Equal(key, L"auth"))
|
||||||
|
{
|
||||||
|
field = FIELD_AUTH;
|
||||||
|
if (Equal(value, L"none"))
|
||||||
|
settings.auth = LG_NET_AUTH_NONE;
|
||||||
|
else if (Equal(value, L"password"))
|
||||||
|
settings.auth = LG_NET_AUTH_PASSWORD;
|
||||||
|
else
|
||||||
|
return Fail(error, L"invalid authentication mode");
|
||||||
|
}
|
||||||
|
else if (Equal(key, L"password"))
|
||||||
|
{
|
||||||
|
field = FIELD_PASSWORD;
|
||||||
|
if (!ValidText(value, 255))
|
||||||
|
return Fail(error, L"invalid password");
|
||||||
|
settings.password = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return Fail(error, L"unknown setting");
|
||||||
|
|
||||||
|
if (fields & field)
|
||||||
|
return Fail(error, L"duplicate setting");
|
||||||
|
fields |= field;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParseNetworkSettings(const std::wstring& source,
|
||||||
|
NetworkSettings& settings, std::wstring * error)
|
||||||
|
{
|
||||||
|
NetworkSettings parsed;
|
||||||
|
if (error)
|
||||||
|
error->clear();
|
||||||
|
|
||||||
|
const std::wstring input = Trim(source);
|
||||||
|
if (input.empty())
|
||||||
|
{
|
||||||
|
settings = std::move(parsed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (input.size() > 4096)
|
||||||
|
return Fail(error, L"settings are too long");
|
||||||
|
|
||||||
|
unsigned fields = 0;
|
||||||
|
size_t begin = 0;
|
||||||
|
while (begin < input.size())
|
||||||
|
{
|
||||||
|
const size_t separator = input.find(L';', begin);
|
||||||
|
const size_t end = separator == std::wstring::npos ?
|
||||||
|
input.size() : separator;
|
||||||
|
const std::wstring field = input.substr(begin, end - begin);
|
||||||
|
const size_t equals = field.find(L'=');
|
||||||
|
if (equals == std::wstring::npos ||
|
||||||
|
!SetField(Trim(field.substr(0, equals)),
|
||||||
|
field.substr(equals + 1), parsed, fields, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (separator == std::wstring::npos)
|
||||||
|
break;
|
||||||
|
begin = separator + 1;
|
||||||
|
if (begin == input.size())
|
||||||
|
return Fail(error, L"empty setting");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.auth == LG_NET_AUTH_PASSWORD && parsed.password.empty())
|
||||||
|
return Fail(error, L"password authentication requires a password");
|
||||||
|
if (parsed.auth == LG_NET_AUTH_NONE && !parsed.password.empty())
|
||||||
|
return Fail(error, L"password requires password authentication");
|
||||||
|
|
||||||
|
settings = std::move(parsed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
45
idd/LGIdd/transport/network/NetworkSettings.h
Normal file
45
idd/LGIdd/transport/network/NetworkSettings.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/network/protocol.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
static constexpr uint16_t NETWORK_DEFAULT_MAX_CLIENTS = 4;
|
||||||
|
static constexpr uint16_t NETWORK_MAX_CLIENTS = 32;
|
||||||
|
|
||||||
|
struct NetworkSettings
|
||||||
|
{
|
||||||
|
std::wstring bindAddress = L"::";
|
||||||
|
uint16_t port = static_cast<uint16_t>(LG_NET_DEFAULT_PORT);
|
||||||
|
uint16_t maxClients = NETWORK_DEFAULT_MAX_CLIENTS;
|
||||||
|
LGNetAuthMode auth = LG_NET_AUTH_MODE_DEFAULT;
|
||||||
|
std::wstring password;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Settings use a semicolon-separated key=value list. Supported keys are
|
||||||
|
// bind, port, maxClients, auth, and password. Password authentication requires
|
||||||
|
// a non-empty password. The default address requests a dual-stack wildcard
|
||||||
|
// listener from the eventual QUIC provider.
|
||||||
|
bool ParseNetworkSettings(const std::wstring& source,
|
||||||
|
NetworkSettings& settings, std::wstring * error = nullptr);
|
||||||
Reference in New Issue
Block a user