diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj
index 1a702282..7569d02e 100644
--- a/idd/LGIdd/LGIdd.vcxproj
+++ b/idd/LGIdd/LGIdd.vcxproj
@@ -60,6 +60,7 @@
+
@@ -107,6 +108,7 @@
+
diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters
index d9268ee3..ef899d4d 100644
--- a/idd/LGIdd/LGIdd.vcxproj.filters
+++ b/idd/LGIdd/LGIdd.vcxproj.filters
@@ -154,6 +154,9 @@
Post-processing\Effects
+
+ Transport
+
Transport
@@ -294,6 +297,9 @@
Post-processing\Effects
+
+ Transport
+
Transport
diff --git a/idd/LGIdd/display/CDeviceContext.cpp b/idd/LGIdd/display/CDeviceContext.cpp
index c5a154e7..c329a64e 100644
--- a/idd/LGIdd/display/CDeviceContext.cpp
+++ b/idd/LGIdd/display/CDeviceContext.cpp
@@ -593,18 +593,23 @@ void CDeviceContext::TransportTimer()
m_transport->Process(*this);
}
-void CDeviceContext::OnSetCursorPos(int32_t x, int32_t y)
+void CDeviceContext::OnSetCursorPos(
+ const SourceKey& source, int32_t x, int32_t y)
{
+ UNREFERENCED_PARAMETER(source);
g_pipe.SetCursorPos(x, y);
}
-void CDeviceContext::OnSetResolution(uint32_t width, uint32_t height)
+void CDeviceContext::OnSetResolution(const SourceKey& source,
+ uint32_t width, uint32_t height)
{
+ UNREFERENCED_PARAMETER(source);
SetResolution(width, height);
}
-void CDeviceContext::OnRecoveryRequest(
+void CDeviceContext::OnRecoveryRequest(const SourceKey& source,
uint64_t session, uint32_t serial, bool active)
{
+ UNREFERENCED_PARAMETER(source);
g_pipe.SetRecovery(this, session, serial, active);
}
diff --git a/idd/LGIdd/display/CDeviceContext.h b/idd/LGIdd/display/CDeviceContext.h
index c3ed9677..48ea40b5 100644
--- a/idd/LGIdd/display/CDeviceContext.h
+++ b/idd/LGIdd/display/CDeviceContext.h
@@ -66,9 +66,11 @@ private:
bool InitializeTransport();
void TransportTimer();
- void OnSetCursorPos(int32_t x, int32_t y) override;
- void OnSetResolution(uint32_t width, uint32_t height) override;
- void OnRecoveryRequest(
+ void OnSetCursorPos(
+ const SourceKey& source, int32_t x, int32_t y) override;
+ void OnSetResolution(const SourceKey& source,
+ uint32_t width, uint32_t height) override;
+ void OnRecoveryRequest(const SourceKey& source,
uint64_t session, uint32_t serial, bool active) override;
void SetResolution(uint32_t width, uint32_t height);
diff --git a/idd/LGIdd/transport/CControlHub.cpp b/idd/LGIdd/transport/CControlHub.cpp
new file mode 100644
index 00000000..179a1751
--- /dev/null
+++ b/idd/LGIdd/transport/CControlHub.cpp
@@ -0,0 +1,161 @@
+/**
+ * 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/CControlHub.h"
+
+#include
+#include
+#include
+
+std::vector> CControlHub::Snapshot() const
+{
+ CSRWSharedLock lock(m_sinkLock);
+ return m_sinks;
+}
+
+bool CControlHub::Add(
+ BackendId backend, uint32_t epoch, IControlTransport& control)
+{
+ std::shared_ptr sink(new (std::nothrow) Sink);
+ if (!sink)
+ return false;
+
+ sink->backend = backend;
+ sink->epoch = epoch;
+ sink->control = &control;
+ {
+ CSRWExclusiveLock lock(m_sinkLock);
+ for (const auto& current : m_sinks)
+ if (current->backend == backend && current->epoch == epoch)
+ return false;
+ m_sinks.push_back(sink);
+ }
+
+ Replay(sink);
+ return true;
+}
+
+void CControlHub::Remove(BackendId backend, uint32_t epoch)
+{
+ std::shared_ptr removed;
+ {
+ CSRWExclusiveLock lock(m_sinkLock);
+ for (auto current = m_sinks.begin(); current != m_sinks.end(); ++current)
+ if ((*current)->backend == backend && (*current)->epoch == epoch)
+ {
+ removed = *current;
+ m_sinks.erase(current);
+ break;
+ }
+ }
+
+ if (removed)
+ {
+ CSRWExclusiveLock lock(removed->lock);
+ removed->control = nullptr;
+ }
+}
+
+void CControlHub::Replay(const std::shared_ptr& sink)
+{
+ IDARG_OUT_QUERY_HWCURSOR cursor = {};
+ std::vector cursorData;
+ UINT sdrWhiteLevel;
+ bool cursorValid;
+ std::shared_ptr transform;
+ {
+ CSRWSharedLock lock(m_stateLock);
+ cursor = m_cursor;
+ cursorData = m_cursorData;
+ sdrWhiteLevel = m_sdrWhiteLevel;
+ cursorValid = m_cursorValid;
+ transform = m_colorTransform;
+ }
+
+ CSRWSharedLock lock(sink->lock);
+ if (!sink->control)
+ return;
+ sink->control->SetColorTransform(std::move(transform));
+ if (cursorValid)
+ sink->control->SendCursor(
+ cursor, cursorData.empty() ? nullptr : cursorData.data(),
+ sdrWhiteLevel);
+}
+
+void CControlHub::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info,
+ const BYTE * data, UINT sdrWhiteLevel)
+{
+ {
+ CSRWExclusiveLock lock(m_stateLock);
+ m_cursor.IsCursorVisible = info.IsCursorVisible;
+ m_cursor.X = info.X;
+ m_cursor.Y = info.Y;
+ m_cursorValid = true;
+ m_sdrWhiteLevel = sdrWhiteLevel;
+
+ if (info.CursorShapeInfo.CursorType !=
+ IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED)
+ {
+ const size_t size =
+ (size_t)info.CursorShapeInfo.Height * info.CursorShapeInfo.Pitch;
+ m_cursor.IsCursorShapeUpdated = info.IsCursorShapeUpdated;
+ m_cursor.CursorShapeInfo = info.CursorShapeInfo;
+ m_cursorData.resize(size);
+ if (size)
+ memcpy(m_cursorData.data(), data, size);
+ m_shapeValid = true;
+ }
+ else if (!m_shapeValid)
+ m_cursor.CursorShapeInfo.CursorType =
+ IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED;
+ }
+
+ const auto sinks = Snapshot();
+ for (const auto& sink : sinks)
+ {
+ CSRWSharedLock lock(sink->lock);
+ if (sink->control)
+ sink->control->SendCursor(info, data, sdrWhiteLevel);
+ }
+}
+
+void CControlHub::SetColorTransform(
+ std::shared_ptr transform)
+{
+ {
+ CSRWExclusiveLock lock(m_stateLock);
+ m_colorTransform = transform;
+ }
+
+ const auto sinks = Snapshot();
+ for (const auto& sink : sinks)
+ {
+ CSRWSharedLock lock(sink->lock);
+ if (sink->control)
+ sink->control->SetColorTransform(transform);
+ }
+}
+
+std::shared_ptr
+CControlHub::GetColorTransform() const
+{
+ CSRWSharedLock lock(m_stateLock);
+ return m_colorTransform;
+}
diff --git a/idd/LGIdd/transport/CControlHub.h b/idd/LGIdd/transport/CControlHub.h
new file mode 100644
index 00000000..9086e2aa
--- /dev/null
+++ b/idd/LGIdd/transport/CControlHub.h
@@ -0,0 +1,65 @@
+/**
+ * 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/IControlTransport.h"
+#include "transport/ITransport.h"
+
+#include
+#include
+
+class CControlHub final : public IControlTransport
+{
+private:
+ struct Sink
+ {
+ BackendId backend;
+ uint32_t epoch;
+ CSRWLock lock;
+ IControlTransport * control;
+ };
+
+ mutable CSRWLock m_sinkLock;
+ std::vector> m_sinks;
+
+ mutable CSRWLock m_stateLock;
+ std::shared_ptr m_colorTransform;
+ IDARG_OUT_QUERY_HWCURSOR m_cursor = {};
+ std::vector m_cursorData;
+ UINT m_sdrWhiteLevel = 0;
+ bool m_cursorValid = false;
+ bool m_shapeValid = false;
+
+ std::vector> Snapshot() const;
+ void Replay(const std::shared_ptr& sink);
+
+public:
+ bool Add(BackendId backend, uint32_t epoch, IControlTransport& control);
+ void Remove(BackendId backend, uint32_t epoch);
+
+ void SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info,
+ const BYTE * data, UINT sdrWhiteLevel) override;
+ void SetColorTransform(
+ std::shared_ptr transform) override;
+ std::shared_ptr
+ GetColorTransform() const override;
+};
diff --git a/idd/LGIdd/transport/CTransportManager.cpp b/idd/LGIdd/transport/CTransportManager.cpp
index 686e579f..c845e8a3 100644
--- a/idd/LGIdd/transport/CTransportManager.cpp
+++ b/idd/LGIdd/transport/CTransportManager.cpp
@@ -24,9 +24,50 @@
#include
#include
+#include
static const uint64_t RETRY_DELAY_MS = 500;
+class CSourceEvents final : public ITransportEvents
+{
+private:
+ BackendId m_backend;
+ uint32_t m_epoch;
+ ITransportEvents& m_events;
+
+ SourceKey Stamp(const SourceKey& source) const
+ {
+ SourceKey stamped = source;
+ stamped.backend = m_backend;
+ stamped.epoch = m_epoch;
+ return stamped;
+ }
+
+public:
+ CSourceEvents(
+ BackendId backend, uint32_t epoch, ITransportEvents& events) :
+ m_backend(backend), m_epoch(epoch), m_events(events) {}
+
+ void OnSetCursorPos(
+ const SourceKey& source, int32_t x, int32_t y) override
+ {
+ m_events.OnSetCursorPos(Stamp(source), x, y);
+ }
+
+ void OnSetResolution(const SourceKey& source,
+ uint32_t width, uint32_t height) override
+ {
+ m_events.OnSetResolution(Stamp(source), width, height);
+ }
+
+ void OnRecoveryRequest(const SourceKey& source,
+ uint64_t session, uint32_t serial, bool active) override
+ {
+ m_events.OnRecoveryRequest(
+ Stamp(source), session, serial, active);
+ }
+};
+
CTransportManager::~CTransportManager()
{
Stop();
@@ -97,6 +138,13 @@ bool CTransportManager::InitializeEntry(Entry& entry)
return false;
}
+ if (!m_control.Add(entry.id, entry.epoch, entry.transport->Control()))
+ {
+ entry.state = State::FAILED;
+ return false;
+ }
+
+ entry.controlAdded = true;
entry.state = State::INITIALIZED;
return true;
}
@@ -187,6 +235,7 @@ void CTransportManager::RetryEntry(Entry& entry, uint64_t now)
if (entry.primary && m_exposed)
return;
+ RemoveServices(entry);
if (entry.transport)
entry.transport->Stop();
entry.transport.reset();
@@ -220,10 +269,12 @@ void CTransportManager::HandleProcessResult(
if (result == ProcessResult::RETRY || !entry.required)
{
+ RemoveServices(entry);
ScheduleRetry(entry);
return;
}
+ RemoveServices(entry);
entry.state = State::FAILED;
}
@@ -238,7 +289,8 @@ ITransport::ProcessResult CTransportManager::Process(
if (entry.state != State::INITIALIZED && entry.state != State::READY)
continue;
- const ProcessResult result = entry.transport->Process(events);
+ CSourceEvents sourceEvents(entry.id, entry.epoch, events);
+ const ProcessResult result = entry.transport->Process(sourceEvents);
HandleProcessResult(entry, result);
}
return ProcessResult::OK;
@@ -250,12 +302,22 @@ void CTransportManager::Stop()
++current)
{
Entry& entry = **current;
+ RemoveServices(entry);
if (entry.transport && entry.state != State::STOPPED)
entry.transport->Stop();
entry.state = State::STOPPED;
}
}
+void CTransportManager::RemoveServices(Entry& entry)
+{
+ if (entry.controlAdded)
+ {
+ m_control.Remove(entry.id, entry.epoch);
+ entry.controlAdded = false;
+ }
+}
+
void CTransportManager::SyncRecovery()
{
for (const auto& current : m_entries)
@@ -299,7 +361,7 @@ IFrameTransport& CTransportManager::Frames()
IControlTransport& CTransportManager::Control()
{
- return Primary().Control();
+ return m_control;
}
IInputTransport * CTransportManager::Input()
diff --git a/idd/LGIdd/transport/CTransportManager.h b/idd/LGIdd/transport/CTransportManager.h
index 8710f20a..3ce3395e 100644
--- a/idd/LGIdd/transport/CTransportManager.h
+++ b/idd/LGIdd/transport/CTransportManager.h
@@ -20,6 +20,7 @@
#pragma once
+#include "transport/CControlHub.h"
#include "transport/ITransport.h"
#include
@@ -53,9 +54,11 @@ private:
State state = State::CLOSED;
uint32_t epoch = 1;
uint64_t retryAt = 0;
+ bool controlAdded = false;
};
std::vector> m_entries;
+ CControlHub m_control;
Entry * m_primary = nullptr;
bool m_initialized = false;
bool m_setup = false;
@@ -68,6 +71,7 @@ private:
void RetryEntry(Entry& entry, uint64_t now);
void HandleProcessResult(Entry& entry, ProcessResult result);
void ScheduleRetry(Entry& entry);
+ void RemoveServices(Entry& entry);
ITransport& Primary();
public:
diff --git a/idd/LGIdd/transport/ITransport.h b/idd/LGIdd/transport/ITransport.h
index 4a3bcf3f..70b2485c 100644
--- a/idd/LGIdd/transport/ITransport.h
+++ b/idd/LGIdd/transport/ITransport.h
@@ -30,22 +30,32 @@ class IControlTransport;
class IFrameTransport;
class IInputTransport;
+using BackendId = uint32_t;
+
+struct SourceKey
+{
+ BackendId backend = 0;
+ uint32_t epoch = 0;
+ uint32_t client = 0;
+ uint32_t generation = 0;
+};
+
class ITransportEvents
{
public:
virtual ~ITransportEvents() = default;
- virtual void OnSetCursorPos(int32_t x, int32_t y) = 0;
- virtual void OnSetResolution(uint32_t width, uint32_t height) = 0;
- virtual void OnRecoveryRequest(
+ virtual void OnSetCursorPos(
+ const SourceKey& source, int32_t x, int32_t y) = 0;
+ virtual void OnSetResolution(
+ const SourceKey& source, uint32_t width, uint32_t height) = 0;
+ virtual void OnRecoveryRequest(const SourceKey& source,
uint64_t session, uint32_t serial, bool active) = 0;
};
class ITransport
{
public:
- using BackendId = uint32_t;
-
enum class OpenResult
{
SUCCESS,
@@ -72,6 +82,8 @@ public:
virtual OpenResult Open() = 0;
virtual bool Initialize() = 0;
virtual bool Setup(size_t alignment) = 0;
+ // Process performs a bounded, non-blocking drain. The events reference is
+ // valid only for the duration of this call and must not be retained.
virtual ProcessResult Process(ITransportEvents& events) = 0;
virtual void Stop() = 0;
virtual void SyncRecovery() {}
diff --git a/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp b/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp
index 965c1917..96823d3d 100644
--- a/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp
+++ b/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp
@@ -97,7 +97,7 @@ ITransport::ProcessResult CLGMPTransport::Process(ITransportEvents& events)
{
const CRecovery::Request recovery = m_recovery.Process();
if (recovery.valid)
- events.OnRecoveryRequest(
+ events.OnRecoveryRequest(SourceKey(),
recovery.session, recovery.serial, recovery.active);
// Before the swap chain establishes the frame-buffer alignment, service
@@ -140,17 +140,21 @@ ITransport::ProcessResult CLGMPTransport::Process(ITransportEvents& events)
{
case KVMFR_MESSAGE_SETCURSORPOS:
{
+ SourceKey source;
+ source.client = sourceClientID;
KVMFRSetCursorPos * position =
reinterpret_cast(msg);
- events.OnSetCursorPos(position->x, position->y);
+ events.OnSetCursorPos(source, position->x, position->y);
break;
}
case KVMFR_MESSAGE_WINDOWSIZE:
{
+ SourceKey source;
+ source.client = sourceClientID;
KVMFRWindowSize * window =
reinterpret_cast(msg);
- events.OnSetResolution(window->w, window->h);
+ events.OnSetResolution(source, window->w, window->h);
break;
}