diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj
index 59174665..98d168bb 100644
--- a/idd/LGIdd/LGIdd.vcxproj
+++ b/idd/LGIdd/LGIdd.vcxproj
@@ -64,6 +64,7 @@
+
@@ -123,6 +124,7 @@
+
diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters
index 723e3880..4940774e 100644
--- a/idd/LGIdd/LGIdd.vcxproj.filters
+++ b/idd/LGIdd/LGIdd.vcxproj.filters
@@ -193,6 +193,9 @@
Transport
+
+ Transport
+
Transport
@@ -324,6 +327,9 @@
Transport
+
+ Transport
+
Transport
diff --git a/idd/LGIdd/config/CSettings.cpp b/idd/LGIdd/config/CSettings.cpp
index a9efdd1a..b431bd19 100644
--- a/idd/LGIdd/config/CSettings.cpp
+++ b/idd/LGIdd/config/CSettings.cpp
@@ -80,6 +80,21 @@ CSettings::DisplayModes CSettings::LoadModes()
return displayModes;
}
+TransportInstances CSettings::LoadTransportInstances() const
+{
+ std::vector entries;
+ if (!ReadMultiStringValue(L"TransportInstances", entries))
+ return DefaultTransportInstances();
+
+ TransportInstances instances;
+ if (!ParseTransportInstances(entries, instances))
+ {
+ DEBUG_WARN("Invalid transport instance configuration; using defaults");
+ return DefaultTransportInstances();
+ }
+ return instances;
+}
+
bool CSettings::SetExtraMode(const DisplayMode& mode)
{
WCHAR buf[64];
@@ -272,7 +287,8 @@ unsigned CSettings::GetDefaultRefreshMilliHz() const
return refreshMilliHz ? refreshMilliHz : 60000;
}
-bool CSettings::ReadModesValue(std::vector &out) const
+bool CSettings::ReadMultiStringValue(const wchar_t * name,
+ std::vector& out) const
{
HKEY hKey = nullptr;
LONG st = RegOpenKeyExW(HKEY_LOCAL_MACHINE, LGIDD_REGKEY, 0, KEY_QUERY_VALUE, &hKey);
@@ -280,29 +296,44 @@ bool CSettings::ReadModesValue(std::vector &out) const
return false;
DWORD type = 0, cb = 0;
- st = RegGetValueW(hKey, nullptr, L"Modes", RRF_RT_REG_MULTI_SZ, &type, nullptr, &cb);
+ st = RegGetValueW(hKey, nullptr, name, RRF_RT_REG_MULTI_SZ,
+ &type, nullptr, &cb);
if (st != ERROR_SUCCESS || cb == 0)
{
RegCloseKey(hKey);
return false;
}
- std::vector buf(cb / sizeof(wchar_t));
- st = RegGetValueW(hKey, nullptr, L"Modes", RRF_RT_REG_MULTI_SZ, &type, buf.data(), &cb);
+ const DWORD capacity = cb;
+ std::vector buf(
+ capacity / sizeof(wchar_t) + 2, L'\0');
+ st = RegGetValueW(hKey, nullptr, name, RRF_RT_REG_MULTI_SZ,
+ &type, buf.data(), &cb);
RegCloseKey(hKey);
- if (st != ERROR_SUCCESS)
+ if (st != ERROR_SUCCESS || cb > capacity || cb % sizeof(wchar_t))
return false;
- const wchar_t* p = buf.data();
- while (*p)
+ const size_t length = cb / sizeof(wchar_t);
+ size_t begin = 0;
+ while (begin < length && buf[begin])
{
- out.emplace_back(p);
- p += (wcslen(p) + 1);
+ size_t end = begin;
+ while (end < length && buf[end])
+ ++end;
+ if (end == length)
+ return false;
+ out.emplace_back(buf.data() + begin, end - begin);
+ begin = end + 1;
}
return !out.empty();
}
+bool CSettings::ReadModesValue(std::vector &out) const
+{
+ return ReadMultiStringValue(L"Modes", out);
+}
+
static std::wstring trim(const std::wstring &s)
{
size_t b = 0, e = s.size();
diff --git a/idd/LGIdd/config/CSettings.h b/idd/LGIdd/config/CSettings.h
index 7076b7c9..82572420 100644
--- a/idd/LGIdd/config/CSettings.h
+++ b/idd/LGIdd/config/CSettings.h
@@ -19,6 +19,8 @@
*/
#pragma once
+#include "transport/TransportConfig.h"
+
#include
#include
#include
@@ -39,6 +41,7 @@ class CSettings
CSettings();
DisplayModes LoadModes();
+ TransportInstances LoadTransportInstances() const;
bool SetExtraMode(const DisplayMode & mode);
bool GetExtraMode(DisplayMode & mode);
unsigned GetDefaultRefreshMilliHz() const;
@@ -47,6 +50,8 @@ class CSettings
bool ReadBoolValue(const wchar_t* name, bool defaultValue = false);
private:
+ bool ReadMultiStringValue(const wchar_t * name,
+ std::vector& out) const;
bool ReadModesValue(std::vector &out) const;
bool ParseModeString(const std::wstring& in, DisplayMode& out);
};
diff --git a/idd/LGIdd/transport/CTransportManager.cpp b/idd/LGIdd/transport/CTransportManager.cpp
index 4ce34cd5..644355c6 100644
--- a/idd/LGIdd/transport/CTransportManager.cpp
+++ b/idd/LGIdd/transport/CTransportManager.cpp
@@ -238,29 +238,34 @@ void CTransportManager::EndCall(Entry& entry,
}
}
-bool CTransportManager::Add(BackendId id, const char * name, bool required,
- bool primary, CreateFn create)
+bool CTransportManager::Add(TransportInstance config, bool primary,
+ CreateFn create)
{
CSRWExclusiveLock managerLock(m_lock);
if (!m_phaseIdle || !m_stoppedEvent || m_phase != Phase::IDLE ||
m_started || m_stopping ||
- m_stopped || !id || !name || !create ||
+ m_stopped || !config.enabled || !config.id || config.kind.empty() ||
+ (config.services & ~TRANSPORT_SERVICE_ALL) || !create ||
m_entryCount == FRAME_MAX_SINKS || (primary && m_primary))
return false;
+ if (primary && (!config.required ||
+ !(config.services & TRANSPORT_SERVICE_FRAME)))
+ return false;
+
for (unsigned i = 0; i < m_entryCount; ++i)
- if (m_entries[i]->id == id)
+ if (m_entries[i]->id == config.id)
return false;
std::unique_ptr entry(new (std::nothrow) Entry);
if (!entry || !entry->idleEvent)
return false;
- entry->id = id;
- entry->name = name;
- entry->required = required;
+ entry->id = config.id;
+ entry->required = config.required;
entry->primary = primary;
entry->create = create;
+ entry->config = std::move(config);
Entry * raw = entry.get();
m_entries[m_entryCount++] = std::move(entry);
@@ -273,15 +278,17 @@ ITransport::OpenResult CTransportManager::OpenEntry(Entry& entry)
{
std::shared_ptr transport;
CreateFn create = nullptr;
+ TransportInstance config;
{
CSRWSharedLock entryLock(entry.lock);
transport = entry.transport;
create = entry.create;
+ config = entry.config;
}
if (!transport)
{
- std::unique_ptr created = create();
+ std::unique_ptr created = create(config);
transport.reset(created.release());
CSRWExclusiveLock entryLock(entry.lock);
entry.transport = transport;
@@ -353,6 +360,8 @@ bool CTransportManager::AddServices(Entry& entry)
BackendId id = 0;
uint32_t epoch = 0;
bool primary = false;
+ bool required = false;
+ uint32_t services = 0;
bool controlAdded = false;
bool controlFailed = false;
bool controlAbsent = false;
@@ -368,6 +377,8 @@ bool CTransportManager::AddServices(Entry& entry)
id = entry.id;
epoch = entry.epoch;
primary = entry.primary;
+ required = entry.required;
+ services = entry.config.services;
controlAdded = entry.controlAdded;
controlFailed = entry.controlFailed;
controlAbsent = entry.controlAbsent;
@@ -380,14 +391,16 @@ bool CTransportManager::AddServices(Entry& entry)
}
if (!transport)
- return !primary;
+ return !required;
const uint64_t now = GetTickCount64();
const bool attach = now >= retryAt;
bool controlRetry = false;
bool frameRetry = false;
bool inputRetry = false;
- if (attach && !controlAdded && !controlFailed && !controlAbsent)
+ if (!(services & TRANSPORT_SERVICE_CONTROL))
+ controlAbsent = true;
+ else if (attach && !controlAdded && !controlFailed && !controlAbsent)
{
IControlSink * control = transport->Control();
if (control && m_control.Add(id, epoch, *control))
@@ -406,7 +419,9 @@ bool CTransportManager::AddServices(Entry& entry)
}
}
- if (attach && !frameAdded && !frameAbsent)
+ if (!(services & TRANSPORT_SERVICE_FRAME))
+ frameAbsent = true;
+ else if (attach && !frameAdded && !frameAbsent)
{
IFrameSink * frame = transport->FrameSink();
if (frame && m_frames.Bind(id, epoch, primary, *frame))
@@ -425,7 +440,9 @@ bool CTransportManager::AddServices(Entry& entry)
}
}
- if (attach && !inputAdded && !inputFailed && !inputAbsent)
+ if (!(services & TRANSPORT_SERVICE_INPUT))
+ inputAbsent = true;
+ else if (attach && !inputAdded && !inputFailed && !inputAbsent)
{
IInputSource * input = transport->Input();
if (input && m_input.Bind(id, epoch, *input))
@@ -450,7 +467,11 @@ bool CTransportManager::AddServices(Entry& entry)
entry.serviceRetryAt = now + SERVICE_RETRY_DELAY_MS;
}
- return !primary || frameAdded;
+ const bool servicesReady =
+ (!(services & TRANSPORT_SERVICE_FRAME) || frameAdded) &&
+ (!(services & TRANSPORT_SERVICE_CONTROL) || controlAdded) &&
+ (!(services & TRANSPORT_SERVICE_INPUT) || inputAdded);
+ return !required || servicesReady;
}
void CTransportManager::HandleServiceFailures()
@@ -650,26 +671,25 @@ void CTransportManager::HandleProcessResult(
bool exposed = false;
bool primary = false;
- bool required = false;
- const char * name = nullptr;
+ std::wstring name;
{
CSRWSharedLock entryLock(entry.lock);
exposed = entry.exposed;
primary = entry.primary;
- required = entry.required;
- name = entry.name;
- }
-
- if (primary && exposed)
- {
- (void)name;
- DEBUG_WARN("Transport %s requested a restart while its frame interfaces "
- "are active", name);
- return;
+ name = entry.config.kind;
}
RemoveServices(entry);
- if (result == ProcessResult::RETRY || !required)
+ if (primary && exposed)
+ {
+ DEBUG_WARN("Transport %ls stopped while its frame interfaces are active",
+ name.c_str());
+ CSRWExclusiveLock entryLock(entry.lock);
+ entry.state = State::FAILED;
+ return;
+ }
+
+ if (result == ProcessResult::RETRY)
{
ScheduleRetry(entry);
return;
@@ -774,6 +794,9 @@ bool CTransportManager::Initialize()
{
CSRWSharedLock entryLock(entry.lock);
transport = entry.transport;
+ if (entry.required && entry.state != State::INITIALIZED &&
+ entry.state != State::READY)
+ success = false;
}
EndCall(entry, transport);
}
@@ -818,7 +841,7 @@ bool CTransportManager::Setup(size_t alignment)
Entry& entry = *entries[i];
if (!BeginCall(entry, Call::LIFECYCLE, true))
{
- if (entry.primary)
+ if (entry.required)
success = false;
continue;
}
@@ -831,14 +854,18 @@ bool CTransportManager::Setup(size_t alignment)
transport = entry.transport;
}
- if ((state == State::INITIALIZED || state == State::READY) &&
- !SetupEntry(entry, alignment))
+ if (state != State::INITIALIZED && state != State::READY)
+ {
+ if (entry.required)
+ success = false;
+ }
+ else if (!SetupEntry(entry, alignment))
{
{
CSRWSharedLock entryLock(entry.lock);
state = entry.state;
}
- if (entry.primary)
+ if (entry.required)
success = false;
else if (state == State::FAILED)
ScheduleRetry(entry);
diff --git a/idd/LGIdd/transport/CTransportManager.h b/idd/LGIdd/transport/CTransportManager.h
index ae3424da..cc12ed94 100644
--- a/idd/LGIdd/transport/CTransportManager.h
+++ b/idd/LGIdd/transport/CTransportManager.h
@@ -25,13 +25,18 @@
#include "transport/CFrameHub.h"
#include "transport/CInputHub.h"
#include "transport/ITransport.h"
+#include "transport/TransportConfig.h"
#include
+static_assert(TRANSPORT_MAX_INSTANCES == FRAME_MAX_SINKS,
+ "The transport and frame limits must match");
+
class CTransportManager final
{
public:
- using CreateFn = std::unique_ptr (*)();
+ using CreateFn = std::unique_ptr (*)(
+ const TransportInstance& config);
using OpenResult = ITransport::OpenResult;
using ProcessResult = ITransport::ProcessResult;
using Recovery = ITransport::Recovery;
@@ -90,7 +95,7 @@ private:
DWORD callOwner = 0;
bool stopRequested = false;
BackendId id = 0;
- const char * name = nullptr;
+ TransportInstance config;
bool required = false;
bool primary = false;
CreateFn create = nullptr;
@@ -166,8 +171,7 @@ public:
CTransportManager(const CTransportManager&) = delete;
CTransportManager& operator=(const CTransportManager&) = delete;
- bool Add(BackendId id, const char * name, bool required, bool primary,
- CreateFn create);
+ bool Add(TransportInstance config, bool primary, CreateFn create);
OpenResult Open();
bool Initialize();
diff --git a/idd/LGIdd/transport/ITransport.h b/idd/LGIdd/transport/ITransport.h
index b6607449..3924f70e 100644
--- a/idd/LGIdd/transport/ITransport.h
+++ b/idd/LGIdd/transport/ITransport.h
@@ -22,6 +22,7 @@
#include "transport/DirectFrameBufferMemory.h"
#include "transport/FrameMemoryLimits.h"
+#include "transport/TransportConfig.h"
#include
#include
@@ -30,8 +31,6 @@ class IControlSink;
class IFrameSink;
class IInputSource;
-using BackendId = uint32_t;
-
struct SourceKey
{
BackendId backend = 0;
diff --git a/idd/LGIdd/transport/TransportConfig.cpp b/idd/LGIdd/transport/TransportConfig.cpp
new file mode 100644
index 00000000..a34025ac
--- /dev/null
+++ b/idd/LGIdd/transport/TransportConfig.cpp
@@ -0,0 +1,373 @@
+/**
+ * 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/TransportConfig.h"
+
+#include
+#include
+#include
+#include
+
+namespace
+{
+ enum Field : unsigned
+ {
+ FIELD_ID = 1U << 0,
+ FIELD_KIND = 1U << 1,
+ FIELD_ENABLED = 1U << 2,
+ FIELD_REQUIRED = 1U << 3,
+ FIELD_SERVICES = 1U << 4,
+ FIELD_PRIORITY = 1U << 5,
+ FIELD_SETTINGS = 1U << 6,
+ };
+
+ 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 ParseBool(const std::wstring& value, bool& result)
+ {
+ if (Equal(value, L"true") || value == L"1")
+ {
+ result = true;
+ return true;
+ }
+ if (Equal(value, L"false") || value == L"0")
+ {
+ result = false;
+ return true;
+ }
+ return false;
+ }
+
+ bool ParseId(const std::wstring& value, BackendId& result)
+ {
+ if (value.empty() || value[0] == L'-')
+ return false;
+
+ wchar_t * end = nullptr;
+ errno = 0;
+ const unsigned long long parsed = wcstoull(value.c_str(), &end, 10);
+ if (errno == ERANGE || !end || *end || !parsed || parsed > UINT32_MAX)
+ return false;
+ result = static_cast(parsed);
+ return true;
+ }
+
+ bool ParsePriority(const std::wstring& value, int32_t& result)
+ {
+ if (value.empty())
+ return false;
+
+ wchar_t * end = nullptr;
+ errno = 0;
+ const long long parsed = wcstoll(value.c_str(), &end, 10);
+ if (errno == ERANGE || !end || *end || parsed < INT32_MIN ||
+ parsed > INT32_MAX)
+ return false;
+ result = static_cast(parsed);
+ return true;
+ }
+
+ bool ParseKind(const std::wstring& value, std::wstring& result)
+ {
+ if (value.empty() || value.size() > 32)
+ return false;
+ for (wchar_t character : value)
+ if (!iswalnum(character) && character != L'_' && character != L'-')
+ return false;
+ result = value;
+ return true;
+ }
+
+ bool AddService(const std::wstring& name, uint32_t& services)
+ {
+ uint32_t service = 0;
+ if (Equal(name, L"frame"))
+ service = TRANSPORT_SERVICE_FRAME;
+ else if (Equal(name, L"control"))
+ service = TRANSPORT_SERVICE_CONTROL;
+ else if (Equal(name, L"input"))
+ service = TRANSPORT_SERVICE_INPUT;
+ else
+ return false;
+
+ if (services & service)
+ return false;
+ services |= service;
+ return true;
+ }
+
+ bool ParseServices(const std::wstring& value, uint32_t& services)
+ {
+ if (Equal(value, L"all"))
+ {
+ services = TRANSPORT_SERVICE_ALL;
+ return true;
+ }
+ if (Equal(value, L"none"))
+ {
+ services = 0;
+ return true;
+ }
+
+ services = 0;
+ size_t begin = 0;
+ while (begin < value.size())
+ {
+ const size_t separator = value.find(L',', begin);
+ const size_t end = separator == std::wstring::npos ?
+ value.size() : separator;
+ if (!AddService(Trim(value.substr(begin, end - begin)), services))
+ return false;
+ if (separator == std::wstring::npos)
+ return true;
+ begin = separator + 1;
+ }
+ return false;
+ }
+
+ bool SetField(const std::wstring& key, const std::wstring& value,
+ TransportInstance& instance, unsigned& fields)
+ {
+ unsigned field = 0;
+ bool valid = false;
+ if (Equal(key, L"id"))
+ {
+ field = FIELD_ID;
+ valid = ParseId(Trim(value), instance.id);
+ }
+ else if (Equal(key, L"kind"))
+ {
+ field = FIELD_KIND;
+ valid = ParseKind(Trim(value), instance.kind);
+ }
+ else if (Equal(key, L"enabled"))
+ {
+ field = FIELD_ENABLED;
+ valid = ParseBool(Trim(value), instance.enabled);
+ }
+ else if (Equal(key, L"required"))
+ {
+ field = FIELD_REQUIRED;
+ valid = ParseBool(Trim(value), instance.required);
+ }
+ else if (Equal(key, L"services"))
+ {
+ field = FIELD_SERVICES;
+ valid = ParseServices(Trim(value), instance.services);
+ }
+ else if (Equal(key, L"priority"))
+ {
+ field = FIELD_PRIORITY;
+ valid = ParsePriority(Trim(value), instance.priority);
+ }
+
+ if (!field || (fields & field) || !valid)
+ return false;
+ fields |= field;
+ return true;
+ }
+
+ bool ParseInstance(const std::wstring& source,
+ TransportInstance& instance)
+ {
+ const std::wstring& line = source;
+ if (Trim(line).empty() || line.size() > 4096)
+ return false;
+
+ unsigned fields = 0;
+ size_t begin = 0;
+ while (begin < line.size())
+ {
+ const size_t separator = line.find(L';', begin);
+ const size_t end = separator == std::wstring::npos ?
+ line.size() : separator;
+ const std::wstring field = line.substr(begin, end - begin);
+ const size_t equals = field.find(L'=');
+ if (equals == std::wstring::npos)
+ return false;
+
+ const std::wstring key = Trim(field.substr(0, equals));
+ if (Equal(key, L"settings"))
+ {
+ if (fields & FIELD_SETTINGS)
+ return false;
+ instance.settings = line.substr(begin + equals + 1);
+ fields |= FIELD_SETTINGS;
+ begin = line.size();
+ break;
+ }
+
+ if (!SetField(key, field.substr(equals + 1), instance, fields))
+ return false;
+ if (separator == std::wstring::npos)
+ break;
+ begin = separator + 1;
+ if (begin == line.size())
+ return false;
+ }
+
+ return (fields & (FIELD_ID | FIELD_KIND)) ==
+ (FIELD_ID | FIELD_KIND);
+ }
+
+ int FindKind(const std::wstring& name,
+ const TransportKind * kinds, unsigned kindCount)
+ {
+ for (unsigned i = 0; i < kindCount; ++i)
+ if (kinds[i].name && Equal(name, kinds[i].name))
+ return static_cast(i);
+ return -1;
+ }
+
+ bool TryResolve(const TransportInstances& instances,
+ const TransportKind * kinds, unsigned kindCount,
+ ResolvedTransportInstances& resolved)
+ {
+ if (!kinds || !kindCount || instances.empty() ||
+ instances.size() > TRANSPORT_MAX_INSTANCES)
+ return false;
+
+ ResolvedTransportInstances result;
+ int primary = -1;
+ for (const TransportInstance& instance : instances)
+ {
+ if (!instance.id || instance.kind.empty() ||
+ (instance.services & ~TRANSPORT_SERVICE_ALL))
+ return false;
+
+ for (const TransportInstance& other : instances)
+ if (&other != &instance && other.id == instance.id)
+ return false;
+
+ const int kindIndex = FindKind(instance.kind, kinds, kindCount);
+ if (kindIndex < 0 || !kinds[kindIndex].activeLimit)
+ return false;
+ if (!instance.enabled)
+ continue;
+
+ unsigned active = 0;
+ for (const ResolvedTransportInstance& existing : result)
+ if (existing.kindIndex == static_cast(kindIndex))
+ ++active;
+ if (active == kinds[kindIndex].activeLimit ||
+ result.size() == TRANSPORT_MAX_INSTANCES)
+ return false;
+
+ ResolvedTransportInstance selected;
+ selected.config = instance;
+ selected.kindIndex = static_cast(kindIndex);
+ if (instance.required &&
+ (instance.services & TRANSPORT_SERVICE_FRAME) &&
+ (primary < 0 || instance.priority >
+ result[primary].config.priority))
+ primary = static_cast(result.size());
+ result.push_back(selected);
+ }
+
+ if (primary < 0)
+ return false;
+ result[primary].primary = true;
+ resolved.swap(result);
+ return true;
+ }
+}
+
+TransportInstances DefaultTransportInstances()
+{
+ TransportInstance instance;
+ instance.id = 1;
+ instance.kind = L"LGMP";
+ instance.enabled = true;
+ instance.required = true;
+ instance.services = TRANSPORT_SERVICE_ALL;
+ instance.priority = 0;
+
+ TransportInstances instances;
+ instances.push_back(instance);
+ return instances;
+}
+
+bool ParseTransportInstances(const std::vector& entries,
+ TransportInstances& instances)
+{
+ TransportInstances parsed;
+ if (entries.empty() || entries.size() > TRANSPORT_MAX_INSTANCES)
+ return false;
+
+ bool hasRequiredFrame = false;
+ for (const std::wstring& entry : entries)
+ {
+ TransportInstance instance;
+ if (!ParseInstance(entry, instance))
+ return false;
+
+ for (const TransportInstance& existing : parsed)
+ if (existing.id == instance.id)
+ return false;
+
+ if (instance.enabled && instance.required &&
+ (instance.services & TRANSPORT_SERVICE_FRAME))
+ hasRequiredFrame = true;
+ parsed.push_back(instance);
+ }
+
+ if (!hasRequiredFrame)
+ return false;
+ instances.swap(parsed);
+ return true;
+}
+
+bool ResolveTransportInstances(const TransportInstances& configured,
+ const TransportKind * kinds, unsigned kindCount,
+ ResolvedTransportInstances& resolved, bool& usedDefaults)
+{
+ usedDefaults = false;
+ if (TryResolve(configured, kinds, kindCount, resolved))
+ return true;
+
+ usedDefaults = true;
+ const TransportInstances defaults = DefaultTransportInstances();
+ if (TryResolve(defaults, kinds, kindCount, resolved))
+ return true;
+
+ resolved.clear();
+ return false;
+}
diff --git a/idd/LGIdd/transport/TransportConfig.h b/idd/LGIdd/transport/TransportConfig.h
new file mode 100644
index 00000000..6223d324
--- /dev/null
+++ b/idd/LGIdd/transport/TransportConfig.h
@@ -0,0 +1,74 @@
+/**
+ * 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
+#include
+#include
+
+using BackendId = uint32_t;
+
+enum TransportService : uint32_t
+{
+ TRANSPORT_SERVICE_FRAME = 1U << 0,
+ TRANSPORT_SERVICE_CONTROL = 1U << 1,
+ TRANSPORT_SERVICE_INPUT = 1U << 2,
+ TRANSPORT_SERVICE_ALL = TRANSPORT_SERVICE_FRAME |
+ TRANSPORT_SERVICE_CONTROL | TRANSPORT_SERVICE_INPUT,
+};
+
+struct TransportInstance
+{
+ BackendId id = 0;
+ std::wstring kind;
+ bool enabled = true;
+ bool required = false;
+ uint32_t services = TRANSPORT_SERVICE_ALL;
+ int32_t priority = 0;
+ std::wstring settings;
+};
+
+using TransportInstances = std::vector;
+
+static const unsigned TRANSPORT_MAX_INSTANCES = 8;
+
+struct TransportKind
+{
+ const wchar_t * name;
+ unsigned activeLimit;
+};
+
+struct ResolvedTransportInstance
+{
+ TransportInstance config;
+ unsigned kindIndex = 0;
+ bool primary = false;
+};
+
+using ResolvedTransportInstances =
+ std::vector;
+
+TransportInstances DefaultTransportInstances();
+bool ParseTransportInstances(const std::vector& entries,
+ TransportInstances& instances);
+bool ResolveTransportInstances(const TransportInstances& configured,
+ const TransportKind * kinds, unsigned kindCount,
+ ResolvedTransportInstances& resolved, bool& usedDefaults);
diff --git a/idd/LGIdd/transport/TransportFactory.cpp b/idd/LGIdd/transport/TransportFactory.cpp
index 0dd5d73b..2d864175 100644
--- a/idd/LGIdd/transport/TransportFactory.cpp
+++ b/idd/LGIdd/transport/TransportFactory.cpp
@@ -20,21 +20,66 @@
#include "transport/TransportFactory.h"
+#include "CDebug.h"
+#include "config/CSettings.h"
#include "transport/CTransportManager.h"
#include "transport/lgmp/CLGMPTransport.h"
#include
-static std::unique_ptr CreateLGMP()
+namespace
{
- return std::unique_ptr(new (std::nothrow) CLGMPTransport());
+ struct Provider
+ {
+ CTransportManager::CreateFn create;
+ };
+
+ std::unique_ptr CreateLGMP(
+ const TransportInstance& config)
+ {
+ return std::unique_ptr(
+ new (std::nothrow) CLGMPTransport(config));
+ }
+
+ const TransportKind KINDS[] =
+ {
+ { L"LGMP", 1 },
+ };
+
+ const Provider PROVIDERS[] =
+ {
+ { CreateLGMP },
+ };
+
+ static_assert(ARRAYSIZE(KINDS) == ARRAYSIZE(PROVIDERS),
+ "Every transport kind must have a provider");
+
+ std::unique_ptr Build(
+ const ResolvedTransportInstances& instances)
+ {
+ std::unique_ptr manager(
+ new (std::nothrow) CTransportManager());
+ if (!manager)
+ return std::unique_ptr();
+
+ for (const ResolvedTransportInstance& instance : instances)
+ if (!manager->Add(instance.config, instance.primary,
+ PROVIDERS[instance.kindIndex].create))
+ return std::unique_ptr();
+ return manager;
+ }
}
std::unique_ptr CreateTransport()
{
- std::unique_ptr manager(
- new (std::nothrow) CTransportManager());
- if (!manager || !manager->Add(1, "LGMP", true, true, CreateLGMP))
+ TransportInstances instances = g_settings.LoadTransportInstances();
+ ResolvedTransportInstances resolved;
+ bool usedDefaults = false;
+ if (!ResolveTransportInstances(instances, KINDS, ARRAYSIZE(KINDS),
+ resolved, usedDefaults))
return std::unique_ptr();
- return manager;
+ if (usedDefaults)
+ DEBUG_WARN("Unsupported transport instance configuration; using defaults");
+
+ return Build(resolved);
}
diff --git a/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp b/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp
index cd7f92b6..ae847cce 100644
--- a/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp
+++ b/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp
@@ -48,7 +48,8 @@ static bool TranslateFrameScheduleFlags(
return true;
}
-CLGMPTransport::CLGMPTransport() :
+CLGMPTransport::CLGMPTransport(const TransportInstance& config) :
+ m_config(config),
m_control(m_host),
m_frames(m_host, m_ivshmem),
m_input(m_host)
diff --git a/idd/LGIdd/transport/lgmp/CLGMPTransport.h b/idd/LGIdd/transport/lgmp/CLGMPTransport.h
index c7730159..4ac4104f 100644
--- a/idd/LGIdd/transport/lgmp/CLGMPTransport.h
+++ b/idd/LGIdd/transport/lgmp/CLGMPTransport.h
@@ -33,6 +33,7 @@
class CLGMPTransport final : public ITransport
{
private:
+ TransportInstance m_config;
// Keep this declaration order. Destruction must release frame and control
// allocations before the LGMP host and its IVSHMEM mapping are destroyed.
CIVSHMEM m_ivshmem;
@@ -44,7 +45,7 @@ private:
std::atomic m_ready = false;
public:
- CLGMPTransport();
+ explicit CLGMPTransport(const TransportInstance& config);
~CLGMPTransport() override = default;
CLGMPTransport(const CLGMPTransport&) = delete;