mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[idd] recovery: restore fallback display
Add a protocol-independent recovery channel outside LGMP so clients can request a usable guest display even when transport versions differ. Synchronize recovery state with the helper across driver restarts and restore the configured topology without persisting temporary changes.
This commit is contained in:
@@ -24,18 +24,30 @@
|
||||
|
||||
static constexpr wchar_t LG_PIPE_NAME[] = L"\\\\.\\pipe\\LookingGlassIDD";
|
||||
|
||||
#pragma pack(push, 4)
|
||||
struct LGPipeMsg
|
||||
{
|
||||
unsigned size;
|
||||
enum
|
||||
enum Type : uint32_t
|
||||
{
|
||||
SETCURSORPOS,
|
||||
SETDISPLAYMODE,
|
||||
GPUSTATUS,
|
||||
RELOADSETTINGS,
|
||||
RESOLUTIONREJECTED
|
||||
RESOLUTIONREJECTED,
|
||||
SET_RECOVERY,
|
||||
RECOVERY_OFF,
|
||||
RECOVERY_ON,
|
||||
RECOVERY_FAILED,
|
||||
RECOVERY_NO_DISPLAY
|
||||
}
|
||||
type;
|
||||
|
||||
enum : uint32_t
|
||||
{
|
||||
RECOVERY_ACTIVE = 0x1U
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
@@ -66,7 +78,15 @@ struct LGPipeMsg
|
||||
uint32_t requiredSizeMiB;
|
||||
}
|
||||
resolutionRejected;
|
||||
|
||||
struct
|
||||
{
|
||||
uint64_t session;
|
||||
uint32_t request;
|
||||
}
|
||||
recovery;
|
||||
};
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(LGPipeMsg) == 20, "LGPipeMsg wire layout changed");
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
<ClCompile Include="postprocess\effect\CRGB24Effect.cpp" />
|
||||
<ClCompile Include="transport\TransportFactory.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CIVSHMEM.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CRecovery.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPControl.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPFrameTransport.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPHost.cpp" />
|
||||
@@ -114,6 +115,7 @@
|
||||
<ClInclude Include="transport\PreparedFrameBuffer.h" />
|
||||
<ClInclude Include="transport\TransportFactory.h" />
|
||||
<ClInclude Include="transport\lgmp\CIVSHMEM.h" />
|
||||
<ClInclude Include="transport\lgmp\CRecovery.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPControl.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPFrameTransport.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPHost.h" />
|
||||
|
||||
@@ -181,6 +181,9 @@
|
||||
<ClInclude Include="transport\lgmp\CIVSHMEM.h">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\lgmp\CRecovery.h">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\lgmp\CLGMPControl.h">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClInclude>
|
||||
@@ -294,6 +297,9 @@
|
||||
<ClCompile Include="transport\lgmp\CIVSHMEM.cpp">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\lgmp\CRecovery.cpp">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\lgmp\CLGMPControl.cpp">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -44,8 +44,14 @@ CDeviceContext::CDeviceContext(WDFDEVICE wdfDevice) :
|
||||
|
||||
CDeviceContext::~CDeviceContext()
|
||||
{
|
||||
// Both callbacks dereference this context. Drain them before the subsystem
|
||||
// These callbacks dereference this context. Drain them before the subsystem
|
||||
// members are destroyed in frame, control, host order.
|
||||
if (m_recoveryHandlerSet)
|
||||
{
|
||||
g_pipe.ClearRecoveryHandler(this);
|
||||
m_recoveryHandlerSet = false;
|
||||
}
|
||||
|
||||
if (m_initTimer)
|
||||
{
|
||||
WdfTimerStop(m_initTimer, TRUE);
|
||||
@@ -379,8 +385,9 @@ void CDeviceContext::FinishInit(UINT connectorIndex)
|
||||
{
|
||||
CDisplayConfiguration::Description description =
|
||||
m_displayConfiguration.GetDescription();
|
||||
m_monitorManager.Create(
|
||||
connectorIndex, m_adapter, std::move(description.edid), this);
|
||||
if (m_monitorManager.Create(
|
||||
connectorIndex, m_adapter, std::move(description.edid), this))
|
||||
m_transport->SyncRecovery();
|
||||
}
|
||||
|
||||
void CDeviceContext::ReplugMonitor()
|
||||
@@ -460,7 +467,90 @@ void CDeviceContext::SetResolution(uint32_t width, uint32_t height)
|
||||
|
||||
bool CDeviceContext::InitializeTransport()
|
||||
{
|
||||
return m_transport && m_transport->Initialize();
|
||||
if (!m_transport)
|
||||
return false;
|
||||
|
||||
if (m_transportTimer)
|
||||
return true;
|
||||
|
||||
g_pipe.SetRecoveryHandler(
|
||||
[](void * opaque, uint64_t session, uint32_t serial, bool active,
|
||||
LGPipeMsg::Type result)
|
||||
{
|
||||
CDeviceContext * context =
|
||||
static_cast<CDeviceContext *>(opaque);
|
||||
|
||||
ITransport::Recovery state = ITransport::Recovery::FAILED;
|
||||
uint32_t error = ERROR_SUCCESS;
|
||||
switch (result)
|
||||
{
|
||||
case LGPipeMsg::RECOVERY_OFF:
|
||||
state = ITransport::Recovery::NORMAL;
|
||||
break;
|
||||
|
||||
case LGPipeMsg::RECOVERY_ON:
|
||||
state = ITransport::Recovery::ACTIVE;
|
||||
break;
|
||||
|
||||
case LGPipeMsg::RECOVERY_FAILED:
|
||||
error = ERROR_GEN_FAILURE;
|
||||
break;
|
||||
|
||||
case LGPipeMsg::RECOVERY_NO_DISPLAY:
|
||||
error = ERROR_NOT_FOUND;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
context->m_transport->RecoveryStatus(
|
||||
session, serial, active, state, error);
|
||||
},
|
||||
this);
|
||||
m_recoveryHandlerSet = true;
|
||||
|
||||
// Claim the pipe recovery channel before initializing the producer session
|
||||
// so no request cached by a prior device context can cross the handoff.
|
||||
if (!m_transport->Initialize())
|
||||
{
|
||||
g_pipe.ClearRecoveryHandler(this);
|
||||
m_recoveryHandlerSet = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
WDF_TIMER_CONFIG config;
|
||||
WDF_TIMER_CONFIG_INIT_PERIODIC(&config,
|
||||
[](WDFTIMER timer) -> void
|
||||
{
|
||||
WDFOBJECT parent = WdfTimerGetParentObject(timer);
|
||||
auto wrapper = WdfObjectGet_CDeviceContextWrapper(parent);
|
||||
wrapper->context->TransportTimer();
|
||||
},
|
||||
10);
|
||||
config.AutomaticSerialization = FALSE;
|
||||
|
||||
/**
|
||||
* Documentation states that Dispatch is not available under UMDF,
|
||||
* however using Passive returns a not-supported error and Dispatch works.
|
||||
*/
|
||||
WDF_OBJECT_ATTRIBUTES attribs;
|
||||
WDF_OBJECT_ATTRIBUTES_INIT(&attribs);
|
||||
attribs.ParentObject = m_wdfDevice;
|
||||
attribs.ExecutionLevel = WdfExecutionLevelDispatch;
|
||||
|
||||
NTSTATUS status = WdfTimerCreate(
|
||||
&config, &attribs, &m_transportTimer);
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
g_pipe.ClearRecoveryHandler(this);
|
||||
m_recoveryHandlerSet = false;
|
||||
DEBUG_ERROR_HR(status, "Transport timer creation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
WdfTimerStart(m_transportTimer, WDF_REL_TIMEOUT_IN_MS(10));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDeviceContext::SetupTransport(size_t alignSize)
|
||||
@@ -471,36 +561,6 @@ bool CDeviceContext::SetupTransport(size_t alignSize)
|
||||
{
|
||||
if (!InitializeTransport() || !m_transport->Setup(alignSize))
|
||||
return false;
|
||||
|
||||
WDF_TIMER_CONFIG config;
|
||||
WDF_TIMER_CONFIG_INIT_PERIODIC(&config,
|
||||
[](WDFTIMER timer) -> void
|
||||
{
|
||||
WDFOBJECT parent = WdfTimerGetParentObject(timer);
|
||||
auto wrapper = WdfObjectGet_CDeviceContextWrapper(parent);
|
||||
wrapper->context->TransportTimer();
|
||||
},
|
||||
10);
|
||||
config.AutomaticSerialization = FALSE;
|
||||
|
||||
/**
|
||||
* Documentation states that Dispatch is not available under UMDF,
|
||||
* however using Passive returns a not-supported error and Dispatch works.
|
||||
*/
|
||||
WDF_OBJECT_ATTRIBUTES attribs;
|
||||
WDF_OBJECT_ATTRIBUTES_INIT(&attribs);
|
||||
attribs.ParentObject = m_wdfDevice;
|
||||
attribs.ExecutionLevel = WdfExecutionLevelDispatch;
|
||||
|
||||
NTSTATUS status = WdfTimerCreate(
|
||||
&config, &attribs, &m_transportTimer);
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DEBUG_ERROR_HR(status, "Timer creation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
WdfTimerStart(m_transportTimer, WDF_REL_TIMEOUT_IN_MS(10));
|
||||
}
|
||||
|
||||
IInputTransport * input = m_transport->Input();
|
||||
@@ -542,3 +602,9 @@ void CDeviceContext::OnSetResolution(uint32_t width, uint32_t height)
|
||||
{
|
||||
SetResolution(width, height);
|
||||
}
|
||||
|
||||
void CDeviceContext::OnRecoveryRequest(
|
||||
uint64_t session, uint32_t serial, bool active)
|
||||
{
|
||||
g_pipe.SetRecovery(this, session, serial, active);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ private:
|
||||
CDisplayConfiguration m_displayConfiguration;
|
||||
CMonitorManager m_monitorManager;
|
||||
|
||||
WDFTIMER m_transportTimer = nullptr;
|
||||
WDFTIMER m_transportTimer = nullptr;
|
||||
bool m_recoveryHandlerSet = false;
|
||||
|
||||
UINT m_iddCxVersion = 0;
|
||||
bool m_hasIddCx110DDIs = false;
|
||||
@@ -67,6 +68,8 @@ private:
|
||||
void TransportTimer();
|
||||
void OnSetCursorPos(int32_t x, int32_t y) override;
|
||||
void OnSetResolution(uint32_t width, uint32_t height) override;
|
||||
void OnRecoveryRequest(
|
||||
uint64_t session, uint32_t serial, bool active) override;
|
||||
void SetResolution(uint32_t width, uint32_t height);
|
||||
|
||||
public:
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "display/CMonitorContext.h"
|
||||
#include "CDebug.h"
|
||||
|
||||
void CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
|
||||
bool CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
|
||||
std::vector<BYTE> edid, CDeviceContext * owner)
|
||||
{
|
||||
DEBUG_INFO("Creating monitor on connector %u", connectorIndex);
|
||||
@@ -38,7 +38,7 @@ void CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
|
||||
if (haveMonitor)
|
||||
{
|
||||
DEBUG_WARN("FinishInit skipped: a monitor already exists");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
WDF_OBJECT_ATTRIBUTES attr;
|
||||
@@ -61,7 +61,7 @@ void CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_ERROR_HR(hr, "Failed to create the monitor container ID");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
IDARG_IN_MONITORCREATE create = {};
|
||||
@@ -73,7 +73,7 @@ void CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DEBUG_ERROR_HR(status, "IddCxMonitorCreate Failed");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Monitor object created (%p)", createOut.MonitorObject);
|
||||
@@ -91,10 +91,11 @@ void CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_ERROR_HR(status, "IddCxMonitorArrival Failed");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Monitor arrival reported successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
CMonitorManager::ReplugAction CMonitorManager::Replug()
|
||||
|
||||
@@ -75,7 +75,7 @@ private:
|
||||
std::atomic<LONG> m_replugQueued = 0;
|
||||
|
||||
public:
|
||||
void Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
|
||||
bool Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
|
||||
std::vector<BYTE> edid, CDeviceContext * owner);
|
||||
ReplugAction Replug();
|
||||
void RequestMode(const CSettings::DisplayMode& mode);
|
||||
|
||||
@@ -52,6 +52,12 @@ void CPipeServer::OnPipeConnected()
|
||||
QueueMsgLocked(queued[i]);
|
||||
break;
|
||||
}
|
||||
|
||||
// Recovery is latched state rather than a one-shot command. Reapply the
|
||||
// latest request whenever the helper reconnects so a helper restart cannot
|
||||
// silently restore the IDD-only topology while recovery is active.
|
||||
if (m_recoveryValid)
|
||||
m_endpoint.Send(&m_recoveryRequest, sizeof(m_recoveryRequest));
|
||||
}
|
||||
|
||||
bool CPipeServer::OnPipeMessage(const void * message, size_t size)
|
||||
@@ -69,6 +75,13 @@ bool CPipeServer::OnPipeMessage(const void * message, size_t size)
|
||||
HandleReloadSettings();
|
||||
return true;
|
||||
|
||||
case LGPipeMsg::RECOVERY_OFF:
|
||||
case LGPipeMsg::RECOVERY_ON:
|
||||
case LGPipeMsg::RECOVERY_FAILED:
|
||||
case LGPipeMsg::RECOVERY_NO_DISPLAY:
|
||||
HandleRecovery(msg);
|
||||
return true;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unknown message type %d", msg.type);
|
||||
return true;
|
||||
@@ -103,12 +116,59 @@ void CPipeServer::HandleReloadSettings()
|
||||
m_deviceContext->ReloadSettings();
|
||||
}
|
||||
|
||||
void CPipeServer::HandleRecovery(const LGPipeMsg & msg)
|
||||
{
|
||||
CSRWSharedLock queueLock(m_queueLock);
|
||||
if (!m_recoveryValid ||
|
||||
msg.recovery.session != m_recoveryRequest.recovery.session ||
|
||||
msg.recovery.request != m_recoveryRequest.recovery.request)
|
||||
{
|
||||
DEBUG_WARN("Ignoring stale recovery status");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t serial =
|
||||
msg.recovery.request & ~LGPipeMsg::RECOVERY_ACTIVE;
|
||||
const bool active =
|
||||
(msg.recovery.request & LGPipeMsg::RECOVERY_ACTIVE) != 0;
|
||||
|
||||
CSRWSharedLock recoveryLock(m_recoveryLock);
|
||||
queueLock.Unlock();
|
||||
if (m_recoveryHandler)
|
||||
m_recoveryHandler(m_recoveryOpaque,
|
||||
msg.recovery.session, serial, active, msg.type);
|
||||
}
|
||||
|
||||
void CPipeServer::SetDeviceContext(CDeviceContext * context)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_deviceContextLock);
|
||||
m_deviceContext = context;
|
||||
}
|
||||
|
||||
void CPipeServer::SetRecoveryHandler(
|
||||
RecoveryHandler handler, void * opaque)
|
||||
{
|
||||
CSRWExclusiveLock queueLock(m_queueLock);
|
||||
CSRWExclusiveLock recoveryLock(m_recoveryLock);
|
||||
m_recoveryValid = false;
|
||||
m_recoveryRequest = {};
|
||||
m_recoveryHandler = handler;
|
||||
m_recoveryOpaque = opaque;
|
||||
}
|
||||
|
||||
void CPipeServer::ClearRecoveryHandler(void * opaque)
|
||||
{
|
||||
CSRWExclusiveLock queueLock(m_queueLock);
|
||||
CSRWExclusiveLock recoveryLock(m_recoveryLock);
|
||||
if (m_recoveryOpaque != opaque)
|
||||
return;
|
||||
|
||||
m_recoveryValid = false;
|
||||
m_recoveryRequest = {};
|
||||
m_recoveryHandler = nullptr;
|
||||
m_recoveryOpaque = nullptr;
|
||||
}
|
||||
|
||||
void CPipeServer::SetCursorPos(uint32_t x, uint32_t y)
|
||||
{
|
||||
// do not send cursor messages if we are not connected or they will end up queued
|
||||
@@ -129,8 +189,8 @@ void CPipeServer::SetDisplayMode(
|
||||
uint32_t width, uint32_t height, uint32_t refreshMilliHz)
|
||||
{
|
||||
LGPipeMsg msg = {};
|
||||
msg.size = sizeof(msg);
|
||||
msg.type = LGPipeMsg::SETDISPLAYMODE;
|
||||
msg.size = sizeof(msg);
|
||||
msg.type = LGPipeMsg::SETDISPLAYMODE;
|
||||
msg.displayMode.width = width;
|
||||
msg.displayMode.height = height;
|
||||
msg.displayMode.refreshMilliHz = refreshMilliHz;
|
||||
@@ -150,10 +210,37 @@ void CPipeServer::ResolutionRejected(uint32_t width, uint32_t height,
|
||||
uint32_t requiredSizeMiB)
|
||||
{
|
||||
LGPipeMsg msg = {};
|
||||
msg.size = sizeof(msg);
|
||||
msg.type = LGPipeMsg::RESOLUTIONREJECTED;
|
||||
msg.resolutionRejected.width = width;
|
||||
msg.resolutionRejected.height = height;
|
||||
msg.size = sizeof(msg);
|
||||
msg.type = LGPipeMsg::RESOLUTIONREJECTED;
|
||||
msg.resolutionRejected.width = width;
|
||||
msg.resolutionRejected.height = height;
|
||||
msg.resolutionRejected.requiredSizeMiB = requiredSizeMiB;
|
||||
WriteMsg(msg);
|
||||
}
|
||||
|
||||
void CPipeServer::SetRecovery(
|
||||
void * owner, uint64_t session, uint32_t serial, bool active)
|
||||
{
|
||||
if (!session || !serial ||
|
||||
(serial & LGPipeMsg::RECOVERY_ACTIVE))
|
||||
{
|
||||
DEBUG_ERROR("Invalid recovery request correlation");
|
||||
return;
|
||||
}
|
||||
|
||||
LGPipeMsg msg = {};
|
||||
msg.size = sizeof(msg);
|
||||
msg.type = LGPipeMsg::SET_RECOVERY;
|
||||
msg.recovery.session = session;
|
||||
msg.recovery.request = serial |
|
||||
(active ? LGPipeMsg::RECOVERY_ACTIVE : 0U);
|
||||
|
||||
CSRWExclusiveLock queueLock(m_queueLock);
|
||||
CSRWSharedLock recoveryLock(m_recoveryLock);
|
||||
if (!m_recoveryHandler || m_recoveryOpaque != owner)
|
||||
return;
|
||||
|
||||
m_recoveryValid = true;
|
||||
m_recoveryRequest = msg;
|
||||
m_endpoint.Send(&msg, sizeof(msg));
|
||||
}
|
||||
|
||||
@@ -33,18 +33,30 @@ class CDeviceContext;
|
||||
|
||||
class CPipeServer : private IPipeEndpointHandler
|
||||
{
|
||||
public:
|
||||
using RecoveryHandler = void (*)(void * opaque,
|
||||
uint64_t session, uint32_t serial, bool active,
|
||||
LGPipeMsg::Type result);
|
||||
|
||||
private:
|
||||
CPipeEndpoint m_endpoint;
|
||||
CSRWLock m_queueLock;
|
||||
std::vector<LGPipeMsg> m_queue;
|
||||
bool m_recoveryValid = false;
|
||||
LGPipeMsg m_recoveryRequest = {};
|
||||
|
||||
CSRWLock m_deviceContextLock;
|
||||
CDeviceContext * m_deviceContext = nullptr;
|
||||
CDeviceContext * m_deviceContext = nullptr;
|
||||
|
||||
CSRWLock m_recoveryLock;
|
||||
RecoveryHandler m_recoveryHandler = nullptr;
|
||||
void * m_recoveryOpaque = nullptr;
|
||||
|
||||
void WriteMsg(const LGPipeMsg & msg);
|
||||
void QueueMsgLocked(const LGPipeMsg & msg);
|
||||
|
||||
void HandleReloadSettings();
|
||||
void HandleRecovery(const LGPipeMsg & msg);
|
||||
|
||||
void OnPipeConnected() override;
|
||||
bool OnPipeMessage(const void * message, size_t size) override;
|
||||
@@ -56,6 +68,8 @@ class CPipeServer : private IPipeEndpointHandler
|
||||
void DeInit();
|
||||
|
||||
void SetDeviceContext(CDeviceContext * context);
|
||||
void SetRecoveryHandler(RecoveryHandler handler, void * opaque);
|
||||
void ClearRecoveryHandler(void * opaque);
|
||||
|
||||
void SetCursorPos(uint32_t x, uint32_t y);
|
||||
void SetDisplayMode(
|
||||
@@ -63,6 +77,8 @@ class CPipeServer : private IPipeEndpointHandler
|
||||
void SetGPUStatus(bool software);
|
||||
void ResolutionRejected(uint32_t width, uint32_t height,
|
||||
uint32_t requiredSizeMiB);
|
||||
void SetRecovery(
|
||||
void * owner, uint64_t session, uint32_t serial, bool active);
|
||||
};
|
||||
|
||||
extern CPipeServer g_pipe;
|
||||
|
||||
@@ -37,6 +37,8 @@ public:
|
||||
|
||||
virtual void OnSetCursorPos(int32_t x, int32_t y) = 0;
|
||||
virtual void OnSetResolution(uint32_t width, uint32_t height) = 0;
|
||||
virtual void OnRecoveryRequest(
|
||||
uint64_t session, uint32_t serial, bool active) = 0;
|
||||
};
|
||||
|
||||
class ITransport
|
||||
@@ -49,12 +51,22 @@ public:
|
||||
FAILURE,
|
||||
};
|
||||
|
||||
enum class Recovery
|
||||
{
|
||||
NORMAL,
|
||||
ACTIVE,
|
||||
FAILED,
|
||||
};
|
||||
|
||||
virtual ~ITransport() = default;
|
||||
|
||||
virtual OpenResult Open() = 0;
|
||||
virtual bool Initialize() = 0;
|
||||
virtual bool Setup(size_t alignment) = 0;
|
||||
virtual void Process(ITransportEvents& events) = 0;
|
||||
virtual void SyncRecovery() {}
|
||||
virtual void RecoveryStatus(
|
||||
uint64_t, uint32_t, bool, Recovery, uint32_t) {}
|
||||
|
||||
virtual FrameMemoryLimits GetMemoryLimits() const = 0;
|
||||
virtual DirectFrameBufferMemory GetDirectMemory() const = 0;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "CDebug.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/KVMFRRecovery.h"
|
||||
|
||||
static bool TranslateFrameScheduleFlags(
|
||||
uint32_t source, uint32_t& destination)
|
||||
@@ -67,6 +68,9 @@ ITransport::OpenResult CLGMPTransport::Open()
|
||||
|
||||
bool CLGMPTransport::Initialize()
|
||||
{
|
||||
if (!m_recovery.Initialize(m_ivshmem))
|
||||
return false;
|
||||
|
||||
if (!m_host.Initialize(m_ivshmem))
|
||||
return false;
|
||||
|
||||
@@ -82,11 +86,26 @@ bool CLGMPTransport::Initialize()
|
||||
|
||||
bool CLGMPTransport::Setup(size_t alignment)
|
||||
{
|
||||
return m_frames.Setup(alignment);
|
||||
if (!m_frames.Setup(alignment))
|
||||
return false;
|
||||
|
||||
m_ready.store(true, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CLGMPTransport::Process(ITransportEvents& events)
|
||||
{
|
||||
const CRecovery::Request recovery = m_recovery.Process();
|
||||
if (recovery.valid)
|
||||
events.OnRecoveryRequest(
|
||||
recovery.session, recovery.serial, recovery.active);
|
||||
|
||||
// Before the swap chain establishes the frame-buffer alignment, service
|
||||
// only the protocol-independent recovery channel. This preserves the old
|
||||
// transport startup boundary while keeping recovery available immediately.
|
||||
if (!m_ready.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
const LGMP_STATUS processStatus = m_host.Process();
|
||||
if (processStatus != LGMP_OK)
|
||||
{
|
||||
@@ -174,6 +193,39 @@ void CLGMPTransport::Process(ITransportEvents& events)
|
||||
m_control.ResendState();
|
||||
}
|
||||
|
||||
void CLGMPTransport::SyncRecovery()
|
||||
{
|
||||
m_recovery.Sync();
|
||||
}
|
||||
|
||||
void CLGMPTransport::RecoveryStatus(
|
||||
uint64_t session, uint32_t serial, bool active,
|
||||
Recovery state, uint32_t error)
|
||||
{
|
||||
uint32_t wireState = KVMFR_R_STATE_FAILED;
|
||||
uint32_t wireError = KVMFR_R_ERR_NONE;
|
||||
switch (state)
|
||||
{
|
||||
case Recovery::NORMAL:
|
||||
wireState = KVMFR_R_STATE_NORMAL;
|
||||
break;
|
||||
|
||||
case Recovery::ACTIVE:
|
||||
wireState = KVMFR_R_STATE_ACTIVE;
|
||||
break;
|
||||
|
||||
case Recovery::FAILED:
|
||||
wireState = KVMFR_R_STATE_FAILED;
|
||||
wireError = error == ERROR_NOT_FOUND ?
|
||||
KVMFR_R_ERR_NO_FALLBACK_DISPLAY :
|
||||
KVMFR_R_ERR_TOPOLOGY_FAILED;
|
||||
break;
|
||||
}
|
||||
|
||||
m_recovery.SetStatus(
|
||||
session, serial, active, wireState, wireError);
|
||||
}
|
||||
|
||||
FrameMemoryLimits CLGMPTransport::GetMemoryLimits() const
|
||||
{
|
||||
return m_frames.GetMemoryLimits();
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
#include "transport/lgmp/CLGMPFrameTransport.h"
|
||||
#include "transport/lgmp/CLGMPHost.h"
|
||||
#include "transport/lgmp/CLGMPInputTransport.h"
|
||||
#include "transport/lgmp/CRecovery.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
class CLGMPTransport final : public ITransport
|
||||
{
|
||||
@@ -37,6 +40,8 @@ private:
|
||||
CLGMPControl m_control;
|
||||
CLGMPFrameTransport m_frames;
|
||||
CLGMPInputTransport m_input;
|
||||
CRecovery m_recovery;
|
||||
std::atomic<bool> m_ready = false;
|
||||
|
||||
public:
|
||||
CLGMPTransport();
|
||||
@@ -49,6 +54,10 @@ public:
|
||||
bool Initialize() override;
|
||||
bool Setup(size_t alignment) override;
|
||||
void Process(ITransportEvents& events) override;
|
||||
void SyncRecovery() override;
|
||||
void RecoveryStatus(
|
||||
uint64_t session, uint32_t serial, bool active,
|
||||
Recovery state, uint32_t error) override;
|
||||
|
||||
FrameMemoryLimits GetMemoryLimits() const override;
|
||||
DirectFrameBufferMemory GetDirectMemory() const override;
|
||||
|
||||
408
idd/LGIdd/transport/lgmp/CRecovery.cpp
Normal file
408
idd/LGIdd/transport/lgmp/CRecovery.cpp
Normal file
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* 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/lgmp/CRecovery.h"
|
||||
|
||||
#include "transport/lgmp/CIVSHMEM.h"
|
||||
#include "platform/CPlatformInfo.h"
|
||||
#include "CDebug.h"
|
||||
#include "VersionInfo.h"
|
||||
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/KVMFRRecovery.h"
|
||||
|
||||
#include <lgmp/lgmp.h>
|
||||
|
||||
#include <Windows.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
static const uint64_t HELPER_TIMEOUT_MS = 30000;
|
||||
CSRWLock l_wireLock;
|
||||
|
||||
uint32_t AtomicRead(uint32_t& value)
|
||||
{
|
||||
return static_cast<uint32_t>(InterlockedCompareExchange(
|
||||
(volatile LONG *)&value, 0, 0));
|
||||
}
|
||||
|
||||
void AtomicWrite(uint32_t& value, uint32_t data)
|
||||
{
|
||||
InterlockedExchange((volatile LONG *)&value, static_cast<LONG>(data));
|
||||
}
|
||||
|
||||
void AtomicIncrement(uint32_t& value)
|
||||
{
|
||||
InterlockedIncrement((volatile LONG *)&value);
|
||||
}
|
||||
|
||||
uint32_t AtomicAdd(uint32_t& value, uint32_t data)
|
||||
{
|
||||
return static_cast<uint32_t>(InterlockedExchangeAdd(
|
||||
(volatile LONG *)&value, static_cast<LONG>(data))) + data;
|
||||
}
|
||||
|
||||
bool AtomicCompareExchange(
|
||||
uint32_t& value, uint32_t expected, uint32_t data)
|
||||
{
|
||||
return static_cast<uint32_t>(InterlockedCompareExchange(
|
||||
(volatile LONG *)&value, static_cast<LONG>(data),
|
||||
static_cast<LONG>(expected))) == expected;
|
||||
}
|
||||
|
||||
uint64_t CreateSession(const void * memory, uint64_t previous)
|
||||
{
|
||||
LARGE_INTEGER counter;
|
||||
QueryPerformanceCounter(&counter);
|
||||
|
||||
uint64_t session = static_cast<uint64_t>(counter.QuadPart) ^
|
||||
(GetTickCount64() << 24) ^
|
||||
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(memory)) ^
|
||||
(static_cast<uint64_t>(GetCurrentProcessId()) << 32) ^
|
||||
GetCurrentThreadId();
|
||||
|
||||
if (!session || session == previous)
|
||||
++session;
|
||||
if (!session)
|
||||
++session;
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
bool CRecovery::OwnsSession()
|
||||
{
|
||||
if (AtomicRead(m_data->header.ready) != KVMFR_R_READY)
|
||||
return false;
|
||||
|
||||
const uint64_t session = m_data->header.session;
|
||||
MemoryBarrier();
|
||||
return session == m_session &&
|
||||
AtomicRead(m_data->header.ready) == KVMFR_R_READY;
|
||||
}
|
||||
|
||||
bool CRecovery::ReadRequest(
|
||||
KVMFRRRequest& source, KVMFRRRequest& result)
|
||||
{
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
{
|
||||
const uint32_t serial = AtomicRead(source.serial);
|
||||
if (!serial || (serial & 1U))
|
||||
return false;
|
||||
|
||||
const uint32_t type = source.request;
|
||||
const uint64_t session = source.session;
|
||||
MemoryBarrier();
|
||||
if (AtomicRead(source.serial) == serial)
|
||||
{
|
||||
result.serial = serial;
|
||||
result.request = type;
|
||||
result.session = session;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CRecovery::ReadStatus(KVMFRRStatus& source, KVMFRRStatus& result)
|
||||
{
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
{
|
||||
const uint32_t serial = AtomicRead(source.serial);
|
||||
if (!serial || (serial & 1U))
|
||||
continue;
|
||||
|
||||
result.ackSerial = source.ackSerial;
|
||||
result.ackRequest = source.ackRequest;
|
||||
result.state = source.state;
|
||||
result.error = source.error;
|
||||
result.session = source.session;
|
||||
MemoryBarrier();
|
||||
if (AtomicRead(source.serial) == serial)
|
||||
{
|
||||
result.serial = serial;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CRecovery::SerialNewer(uint32_t serial, uint32_t reference)
|
||||
{
|
||||
const uint32_t difference = serial - reference;
|
||||
return difference && difference < 0x80000000U;
|
||||
}
|
||||
|
||||
uint32_t CRecovery::NextTicket()
|
||||
{
|
||||
uint32_t ticket = AtomicAdd(m_data->req.ticket, 2U);
|
||||
if (!ticket)
|
||||
ticket = AtomicAdd(m_data->req.ticket, 2U);
|
||||
return ticket;
|
||||
}
|
||||
|
||||
void CRecovery::Publish(uint32_t serial, uint32_t request,
|
||||
uint32_t state, uint32_t error)
|
||||
{
|
||||
const uint32_t writing = m_statusSerial | 1U;
|
||||
uint32_t published = writing + 1U;
|
||||
if (!published)
|
||||
published = KVMFR_R_REQ_FIRST;
|
||||
|
||||
AtomicWrite(m_data->status.serial, writing);
|
||||
m_data->status.ackRequest = request;
|
||||
m_data->status.state = state;
|
||||
m_data->status.error = error;
|
||||
m_data->status.session = m_session;
|
||||
m_data->status.ackSerial = serial;
|
||||
AtomicWrite(m_data->status.serial, published);
|
||||
m_statusSerial = published;
|
||||
}
|
||||
|
||||
bool CRecovery::Initialize(CIVSHMEM& ivshmem)
|
||||
{
|
||||
CSRWExclusiveLock wireLock(l_wireLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_data)
|
||||
return OwnsSession();
|
||||
|
||||
m_data = static_cast<KVMFRR *>(ivshmem.GetRecoveryMem());
|
||||
if (!m_data)
|
||||
{
|
||||
DEBUG_ERROR("IVSHMEM is too small for the recovery region");
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t oldReady = AtomicRead(m_data->header.ready);
|
||||
const bool oldValid = oldReady == KVMFR_R_READY &&
|
||||
memcmp(m_data->header.magic, KVMFR_R_MAGIC,
|
||||
sizeof(m_data->header.magic)) == 0 &&
|
||||
m_data->header.abiVersion == KVMFR_R_VERSION &&
|
||||
m_data->header.structSize >= sizeof(KVMFRR) &&
|
||||
m_data->header.session != 0;
|
||||
const uint64_t oldSession = oldValid ? m_data->header.session : 0;
|
||||
|
||||
uint32_t retainedRequest = KVMFR_R_REQ_NONE;
|
||||
if (oldValid)
|
||||
{
|
||||
KVMFRRStatus status = {};
|
||||
if (ReadStatus(m_data->status, status) &&
|
||||
status.session == oldSession)
|
||||
{
|
||||
if (status.ackRequest == KVMFR_R_REQ_RECOVERY &&
|
||||
(status.state == KVMFR_R_STATE_SWITCHING ||
|
||||
status.state == KVMFR_R_STATE_ACTIVE ||
|
||||
status.state == KVMFR_R_STATE_FAILED))
|
||||
retainedRequest = KVMFR_R_REQ_RECOVERY;
|
||||
else if (status.ackRequest == KVMFR_R_REQ_NORMAL &&
|
||||
(status.state == KVMFR_R_STATE_SWITCHING ||
|
||||
status.state == KVMFR_R_STATE_FAILED))
|
||||
retainedRequest = KVMFR_R_REQ_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
AtomicWrite(m_data->header.ready, 0);
|
||||
if (oldValid)
|
||||
{
|
||||
ZeroMemory(&m_data->header, sizeof(m_data->header));
|
||||
ZeroMemory(&m_data->info, sizeof(m_data->info));
|
||||
ZeroMemory(&m_data->status, sizeof(m_data->status));
|
||||
|
||||
// Preserve the client-owned ticket and interrupted odd slots. Completed
|
||||
// requests belong to the old producer session and can now be reclaimed.
|
||||
for (unsigned i = 0; i < KVMFR_R_REQ_SLOTS; ++i)
|
||||
{
|
||||
KVMFRRRequest request = {};
|
||||
if (ReadRequest(m_data->requests[i], request))
|
||||
AtomicCompareExchange(
|
||||
m_data->requests[i].serial, request.serial, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
ZeroMemory(m_data, sizeof(*m_data));
|
||||
|
||||
m_session = CreateSession(m_data, oldSession);
|
||||
|
||||
memcpy(m_data->header.magic, KVMFR_R_MAGIC,
|
||||
sizeof(m_data->header.magic));
|
||||
m_data->header.abiVersion = KVMFR_R_VERSION;
|
||||
m_data->header.structSize = static_cast<uint16_t>(sizeof(*m_data));
|
||||
m_data->header.capabilities = KVMFR_R_CAP_DISPLAY;
|
||||
m_data->header.lgmpVersion = LGMP_PROTOCOL_VERSION;
|
||||
m_data->header.kvmfrVersion = KVMFR_VERSION;
|
||||
m_data->header.session = m_session;
|
||||
memcpy(m_data->header.uuid, CPlatformInfo::GetUUID(),
|
||||
sizeof(m_data->header.uuid));
|
||||
m_data->header.heartbeat = 1;
|
||||
strncpy_s(m_data->info.version, sizeof(m_data->info.version),
|
||||
LG_VERSION_STR, _TRUNCATE);
|
||||
|
||||
m_request = retainedRequest == KVMFR_R_REQ_NONE ?
|
||||
KVMFR_R_REQ_NORMAL : retainedRequest;
|
||||
m_lastSerial = NextTicket();
|
||||
m_replay = true;
|
||||
Publish(m_lastSerial, m_request,
|
||||
KVMFR_R_STATE_SWITCHING, KVMFR_R_ERR_NONE);
|
||||
|
||||
m_nextHeartbeat = GetTickCount64() + KVMFR_R_HEARTBEAT_MS;
|
||||
AtomicWrite(m_data->header.ready, KVMFR_R_READY);
|
||||
|
||||
DEBUG_INFO("Recovery channel initialized (session %llu%s)",
|
||||
(unsigned long long)m_session,
|
||||
retainedRequest != KVMFR_R_REQ_NONE ? ", request retained" : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
void CRecovery::Sync()
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
m_syncReady = true;
|
||||
}
|
||||
|
||||
CRecovery::Request CRecovery::Process()
|
||||
{
|
||||
Request result;
|
||||
CSRWExclusiveLock wireLock(l_wireLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!m_data || !OwnsSession())
|
||||
return result;
|
||||
|
||||
const uint64_t now = GetTickCount64();
|
||||
if (now >= m_nextHeartbeat)
|
||||
{
|
||||
AtomicIncrement(m_data->header.heartbeat);
|
||||
m_nextHeartbeat = now + KVMFR_R_HEARTBEAT_MS;
|
||||
}
|
||||
|
||||
KVMFRRRequest requests[KVMFR_R_REQ_SLOTS] = {};
|
||||
bool stable[KVMFR_R_REQ_SLOTS] = {};
|
||||
KVMFRRRequest request = {};
|
||||
bool haveRequest = false;
|
||||
for (unsigned i = 0; i < KVMFR_R_REQ_SLOTS; ++i)
|
||||
{
|
||||
stable[i] = ReadRequest(m_data->requests[i], requests[i]);
|
||||
if (!stable[i] || requests[i].session != m_session ||
|
||||
!SerialNewer(requests[i].serial, m_lastSerial))
|
||||
continue;
|
||||
|
||||
if (!haveRequest || SerialNewer(requests[i].serial, request.serial))
|
||||
{
|
||||
request = requests[i];
|
||||
haveRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (haveRequest)
|
||||
{
|
||||
m_lastSerial = request.serial;
|
||||
|
||||
if (request.session == m_session &&
|
||||
(request.request == KVMFR_R_REQ_NORMAL ||
|
||||
request.request == KVMFR_R_REQ_RECOVERY))
|
||||
{
|
||||
m_request = request.request;
|
||||
m_replay = m_request == KVMFR_R_REQ_NORMAL && !m_syncReady;
|
||||
m_waiting = false;
|
||||
Publish(m_lastSerial, m_request,
|
||||
KVMFR_R_STATE_SWITCHING, KVMFR_R_ERR_NONE);
|
||||
|
||||
if (m_replay)
|
||||
DEBUG_INFO("Deferring recovery request %u until monitor arrival",
|
||||
m_lastSerial);
|
||||
else
|
||||
{
|
||||
m_waiting = true;
|
||||
m_deadline = now + HELPER_TIMEOUT_MS;
|
||||
|
||||
result.session = m_session;
|
||||
result.serial = m_lastSerial;
|
||||
result.valid = true;
|
||||
result.active = m_request == KVMFR_R_REQ_RECOVERY;
|
||||
DEBUG_INFO("Recovery mode request %u: %s", m_lastSerial,
|
||||
result.active ? "active" : "normal");
|
||||
}
|
||||
}
|
||||
else if (request.session == m_session)
|
||||
{
|
||||
m_request = request.request;
|
||||
m_replay = false;
|
||||
m_waiting = false;
|
||||
Publish(m_lastSerial, m_request,
|
||||
KVMFR_R_STATE_FAILED, KVMFR_R_ERR_UNSUPPORTED);
|
||||
DEBUG_WARN("Ignoring invalid recovery request %u", m_lastSerial);
|
||||
}
|
||||
}
|
||||
else if (m_replay &&
|
||||
(m_request != KVMFR_R_REQ_NORMAL || m_syncReady))
|
||||
{
|
||||
m_replay = false;
|
||||
m_waiting = true;
|
||||
m_deadline = now + HELPER_TIMEOUT_MS;
|
||||
|
||||
result.session = m_session;
|
||||
result.serial = m_lastSerial;
|
||||
result.valid = true;
|
||||
result.active = m_request == KVMFR_R_REQ_RECOVERY;
|
||||
DEBUG_INFO("Synchronizing recovery helper mode: %s",
|
||||
result.active ? "active" : "normal");
|
||||
}
|
||||
|
||||
// A stable slot cannot be reused until the producer clears it. Publish the
|
||||
// selected request's state first so its client cannot observe a reclaimed
|
||||
// slot without a corresponding acknowledgement.
|
||||
for (unsigned i = 0; i < KVMFR_R_REQ_SLOTS; ++i)
|
||||
if (stable[i])
|
||||
AtomicCompareExchange(
|
||||
m_data->requests[i].serial, requests[i].serial, 0);
|
||||
|
||||
if (m_waiting && now >= m_deadline)
|
||||
{
|
||||
m_waiting = false;
|
||||
Publish(m_lastSerial, m_request,
|
||||
KVMFR_R_STATE_FAILED, KVMFR_R_ERR_HELPER_UNAVAILABLE);
|
||||
DEBUG_WARN("Recovery helper did not respond to request %u",
|
||||
m_lastSerial);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CRecovery::SetStatus(uint64_t session, uint32_t serial, bool active,
|
||||
uint32_t state, uint32_t error)
|
||||
{
|
||||
CSRWExclusiveLock wireLock(l_wireLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
const bool expectedActive = m_request == KVMFR_R_REQ_RECOVERY;
|
||||
if (!m_data || !OwnsSession() || session != m_session ||
|
||||
serial != m_lastSerial ||
|
||||
active != expectedActive ||
|
||||
(state == KVMFR_R_STATE_ACTIVE && !active) ||
|
||||
(state == KVMFR_R_STATE_NORMAL && active))
|
||||
{
|
||||
DEBUG_WARN("Ignoring stale recovery helper status");
|
||||
return;
|
||||
}
|
||||
|
||||
m_waiting = false;
|
||||
Publish(serial, m_request, state, error);
|
||||
DEBUG_INFO("Recovery request %u completed with state %u", serial, state);
|
||||
}
|
||||
72
idd/LGIdd/transport/lgmp/CRecovery.h
Normal file
72
idd/LGIdd/transport/lgmp/CRecovery.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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 <stdint.h>
|
||||
|
||||
struct KVMFRR;
|
||||
struct KVMFRRRequest;
|
||||
struct KVMFRRStatus;
|
||||
|
||||
class CIVSHMEM;
|
||||
|
||||
class CRecovery
|
||||
{
|
||||
public:
|
||||
struct Request
|
||||
{
|
||||
uint64_t session = 0;
|
||||
uint32_t serial = 0;
|
||||
bool valid = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
private:
|
||||
CSRWLock m_lock;
|
||||
KVMFRR * m_data = nullptr;
|
||||
|
||||
uint64_t m_session = 0;
|
||||
uint64_t m_nextHeartbeat = 0;
|
||||
uint64_t m_deadline = 0;
|
||||
uint32_t m_lastSerial = 0;
|
||||
uint32_t m_statusSerial = 0;
|
||||
uint32_t m_request = 0;
|
||||
bool m_syncReady = false;
|
||||
bool m_replay = false;
|
||||
bool m_waiting = false;
|
||||
|
||||
static bool ReadRequest(KVMFRRRequest& source, KVMFRRRequest& result);
|
||||
static bool ReadStatus(KVMFRRStatus& source, KVMFRRStatus& result);
|
||||
static bool SerialNewer(uint32_t serial, uint32_t reference);
|
||||
bool OwnsSession();
|
||||
uint32_t NextTicket();
|
||||
void Publish(uint32_t serial, uint32_t request,
|
||||
uint32_t state, uint32_t error);
|
||||
|
||||
public:
|
||||
bool Initialize(CIVSHMEM& ivshmem);
|
||||
void Sync();
|
||||
Request Process();
|
||||
void SetStatus(uint64_t session, uint32_t serial, bool active,
|
||||
uint32_t state, uint32_t error);
|
||||
};
|
||||
@@ -30,6 +30,7 @@
|
||||
#define WM_CLEAN_UP_CONFIG (WM_USER+1)
|
||||
#define WM_NO_GPU (WM_USER+2)
|
||||
#define WM_RESOLUTION_REJECTED (WM_USER+3)
|
||||
#define WM_RECOVERY_STATE (WM_USER+4)
|
||||
|
||||
#define ID_MENU_SHOW_LOG 3000
|
||||
#define ID_MENU_SHOW_CONFIG 3001
|
||||
@@ -66,7 +67,7 @@ bool CNotifyWindow::registerClass()
|
||||
}
|
||||
|
||||
CNotifyWindow::CNotifyWindow() : m_iconData({ 0 }), m_iconRegistered(false),
|
||||
m_menu(CreatePopupMenu()), closeRequested(false)
|
||||
m_menu(CreatePopupMenu()), closeRequested(false), m_recoveryActive(false)
|
||||
{
|
||||
CreateWindowEx(0, MAKEINTATOM(s_atom), NULL,
|
||||
0, 0, 0, 0, 0, NULL, NULL, hInstance, this);
|
||||
@@ -112,8 +113,16 @@ LRESULT CNotifyWindow::handleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_RECOVERY_STATE:
|
||||
m_recoveryActive = wParam;
|
||||
KillTimer(m_hwnd, ID_DISPLAY_CHECK_TIMER);
|
||||
if (!m_recoveryActive)
|
||||
scheduleDisplayCheck(DISPLAY_SETTLE_DELAY);
|
||||
return 0;
|
||||
|
||||
case WM_DISPLAYCHANGE:
|
||||
scheduleDisplayCheck(DISPLAY_SETTLE_DELAY);
|
||||
if (!m_recoveryActive)
|
||||
scheduleDisplayCheck(DISPLAY_SETTLE_DELAY);
|
||||
return 0;
|
||||
|
||||
case WM_TIMER:
|
||||
@@ -121,6 +130,8 @@ LRESULT CNotifyWindow::handleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
case ID_DISPLAY_CHECK_TIMER:
|
||||
KillTimer(m_hwnd, ID_DISPLAY_CHECK_TIMER);
|
||||
if (m_recoveryActive)
|
||||
break;
|
||||
if (m_onEnsureOnlyDisplay && m_onEnsureOnlyDisplay())
|
||||
DEBUG_INFO("Enforced Looking Glass as the only display");
|
||||
else
|
||||
@@ -293,6 +304,12 @@ void CNotifyWindow::notifyResolutionRejected(uint32_t width, uint32_t height,
|
||||
}
|
||||
}
|
||||
|
||||
void CNotifyWindow::setRecoveryMode(bool active)
|
||||
{
|
||||
if (!PostMessage(m_hwnd, WM_RECOVERY_STATE, active, 0))
|
||||
DEBUG_ERROR_HR(GetLastError(), "Failed to update recovery state");
|
||||
}
|
||||
|
||||
void CNotifyWindow::handleResolutionRejected(uint32_t width, uint32_t height,
|
||||
uint32_t requiredSizeMiB)
|
||||
{
|
||||
@@ -326,7 +343,7 @@ void CNotifyWindow::close()
|
||||
|
||||
void CNotifyWindow::scheduleDisplayCheck(UINT delay)
|
||||
{
|
||||
if (!m_onEnsureOnlyDisplay)
|
||||
if (m_recoveryActive || !m_onEnsureOnlyDisplay)
|
||||
return;
|
||||
|
||||
CRegistrySettings settings;
|
||||
|
||||
@@ -37,6 +37,7 @@ class CNotifyWindow : public CWindow
|
||||
std::optional<bool> m_gpuQueue;
|
||||
HMENU m_menu;
|
||||
bool closeRequested;
|
||||
bool m_recoveryActive;
|
||||
std::unique_ptr<CConfigWindow> m_config;
|
||||
|
||||
std::function<void()> m_onSettingChange;
|
||||
@@ -76,6 +77,7 @@ public:
|
||||
void setGPU(bool hasGPU);
|
||||
void notifyResolutionRejected(uint32_t width, uint32_t height,
|
||||
uint32_t requiredSizeMiB);
|
||||
void setRecoveryMode(bool active);
|
||||
|
||||
HWND hwndDialog();
|
||||
void close();
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "CDebug.h"
|
||||
#include "CSRWLock.h"
|
||||
#include "CNotifyWindow.h"
|
||||
#include "CRegistrySettings.h"
|
||||
|
||||
#include <setupapi.h>
|
||||
#include <tchar.h>
|
||||
@@ -29,6 +30,11 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
static const unsigned RECOVERY_VERIFY_ATTEMPTS = 20;
|
||||
static const unsigned RECOVERY_PATH_ATTEMPTS = 20;
|
||||
static const size_t RECOVERY_MAX_PATHS = 4;
|
||||
static const DWORD RECOVERY_VERIFY_DELAY_MS = 100;
|
||||
|
||||
struct DisplayState
|
||||
{
|
||||
DISPLAY_DEVICE device;
|
||||
@@ -65,6 +71,90 @@ namespace
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ContainsNoCase(LPCTSTR text, LPCTSTR value)
|
||||
{
|
||||
const size_t length = _tcslen(value);
|
||||
for (; *text; ++text)
|
||||
if (_tcsnicmp(text, value, length) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsLGPath(const DISPLAYCONFIG_PATH_INFO& path)
|
||||
{
|
||||
DISPLAYCONFIG_TARGET_DEVICE_NAME target = {};
|
||||
target.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
|
||||
target.header.size = sizeof(target);
|
||||
target.header.adapterId = path.targetInfo.adapterId;
|
||||
target.header.id = path.targetInfo.id;
|
||||
if (DisplayConfigGetDeviceInfo(&target.header) == ERROR_SUCCESS &&
|
||||
(_tcsicmp(target.monitorFriendlyDeviceName,
|
||||
_T("Looking Glass")) == 0 ||
|
||||
ContainsNoCase(target.monitorDevicePath, _T("LGD1DDD")) ||
|
||||
ContainsNoCase(target.monitorDevicePath, _T("ROOT#LGIDD"))))
|
||||
return true;
|
||||
|
||||
DISPLAYCONFIG_SOURCE_DEVICE_NAME source = {};
|
||||
source.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
|
||||
source.header.size = sizeof(source);
|
||||
source.header.adapterId = path.sourceInfo.adapterId;
|
||||
source.header.id = path.sourceInfo.id;
|
||||
if (DisplayConfigGetDeviceInfo(&source.header) != ERROR_SUCCESS ||
|
||||
!source.viewGdiDeviceName[0])
|
||||
return false;
|
||||
|
||||
DISPLAY_DEVICE device = {};
|
||||
device.cb = sizeof(device);
|
||||
for (DWORD i = 0; EnumDisplayDevices(NULL, i, &device, 0); ++i)
|
||||
{
|
||||
if (_tcsicmp(device.DeviceName, source.viewGdiDeviceName) == 0)
|
||||
return IsLGDisplay(device);
|
||||
|
||||
device = {};
|
||||
device.cb = sizeof(device);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SameTarget(const DISPLAYCONFIG_PATH_INFO& a,
|
||||
const DISPLAYCONFIG_PATH_INFO& b)
|
||||
{
|
||||
return a.targetInfo.adapterId.HighPart ==
|
||||
b.targetInfo.adapterId.HighPart &&
|
||||
a.targetInfo.adapterId.LowPart ==
|
||||
b.targetInfo.adapterId.LowPart &&
|
||||
a.targetInfo.id == b.targetInfo.id;
|
||||
}
|
||||
|
||||
uint32_t QueryAllPaths(std::vector<DISPLAYCONFIG_PATH_INFO>& paths)
|
||||
{
|
||||
for (unsigned int attempt = 0; attempt < 3; ++attempt)
|
||||
{
|
||||
UINT32 pathCount = 0;
|
||||
UINT32 modeCount = 0;
|
||||
LONG result = GetDisplayConfigBufferSizes(
|
||||
QDC_ALL_PATHS, &pathCount, &modeCount);
|
||||
if (result != ERROR_SUCCESS)
|
||||
return static_cast<uint32_t>(result);
|
||||
|
||||
paths.resize(pathCount);
|
||||
std::vector<DISPLAYCONFIG_MODE_INFO> modes(modeCount);
|
||||
result = QueryDisplayConfig(QDC_ALL_PATHS,
|
||||
&pathCount, paths.data(), &modeCount, modes.data(), NULL);
|
||||
if (result == ERROR_INSUFFICIENT_BUFFER)
|
||||
continue;
|
||||
if (result != ERROR_SUCCESS)
|
||||
return static_cast<uint32_t>(result);
|
||||
|
||||
paths.resize(pathCount);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
return ERROR_INSUFFICIENT_BUFFER;
|
||||
}
|
||||
|
||||
bool GetDisplayStates(std::vector<DisplayState>& displays, size_t& lgIndex)
|
||||
{
|
||||
lgIndex = SIZE_MAX;
|
||||
@@ -77,9 +167,9 @@ namespace
|
||||
!(device.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
|
||||
{
|
||||
DisplayState state = {};
|
||||
state.device = device;
|
||||
state.device = device;
|
||||
state.mode.dmSize = sizeof(state.mode);
|
||||
state.isLG = IsLGDisplay(device);
|
||||
state.isLG = IsLGDisplay(device);
|
||||
|
||||
if (!EnumDisplaySettingsEx(device.DeviceName, ENUM_CURRENT_SETTINGS,
|
||||
&state.mode, 0))
|
||||
@@ -101,6 +191,191 @@ namespace
|
||||
|
||||
return lgIndex != SIZE_MAX;
|
||||
}
|
||||
|
||||
bool HasActiveDisplay(bool lg)
|
||||
{
|
||||
DISPLAY_DEVICE device = {};
|
||||
device.cb = sizeof(device);
|
||||
for (DWORD i = 0; EnumDisplayDevices(NULL, i, &device, 0); ++i)
|
||||
{
|
||||
if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) &&
|
||||
!(device.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) &&
|
||||
IsLGDisplay(device) == lg)
|
||||
return true;
|
||||
|
||||
device = {};
|
||||
device.cb = sizeof(device);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WaitForDisplay(bool lg,
|
||||
unsigned int attempts = RECOVERY_VERIFY_ATTEMPTS)
|
||||
{
|
||||
for (unsigned int attempt = 0;
|
||||
attempt < attempts;
|
||||
++attempt)
|
||||
{
|
||||
if (HasActiveDisplay(lg))
|
||||
return true;
|
||||
|
||||
if (attempt + 1 < attempts)
|
||||
Sleep(RECOVERY_VERIFY_DELAY_MS);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasOnlyLGDisplay()
|
||||
{
|
||||
bool found = false;
|
||||
DISPLAY_DEVICE device = {};
|
||||
device.cb = sizeof(device);
|
||||
for (DWORD i = 0; EnumDisplayDevices(NULL, i, &device, 0); ++i)
|
||||
{
|
||||
if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) &&
|
||||
!(device.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
|
||||
{
|
||||
if (!IsLGDisplay(device))
|
||||
return false;
|
||||
found = true;
|
||||
}
|
||||
|
||||
device = {};
|
||||
device.cb = sizeof(device);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
bool WaitForOnlyLGDisplay()
|
||||
{
|
||||
for (unsigned int attempt = 0;
|
||||
attempt < RECOVERY_VERIFY_ATTEMPTS;
|
||||
++attempt)
|
||||
{
|
||||
if (HasOnlyLGDisplay())
|
||||
return true;
|
||||
|
||||
if (attempt + 1 < RECOVERY_VERIFY_ATTEMPTS)
|
||||
Sleep(RECOVERY_VERIFY_DELAY_MS);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t ActivateDisplay(bool lg)
|
||||
{
|
||||
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
|
||||
const uint32_t queryError = QueryAllPaths(paths);
|
||||
if (queryError != ERROR_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("Failed to enumerate display paths (%u)", queryError);
|
||||
return queryError;
|
||||
}
|
||||
|
||||
std::vector<DISPLAYCONFIG_PATH_INFO> attempted;
|
||||
uint32_t lastError = ERROR_NOT_FOUND;
|
||||
for (const DISPLAYCONFIG_PATH_INFO& path : paths)
|
||||
{
|
||||
if (!path.targetInfo.targetAvailable || IsLGPath(path) != lg)
|
||||
continue;
|
||||
|
||||
bool duplicate = false;
|
||||
for (const DISPLAYCONFIG_PATH_INFO& previous : attempted)
|
||||
if (SameTarget(path, previous))
|
||||
{
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
if (duplicate)
|
||||
continue;
|
||||
|
||||
if (attempted.size() >= RECOVERY_MAX_PATHS)
|
||||
break;
|
||||
attempted.emplace_back(path);
|
||||
|
||||
DISPLAYCONFIG_PATH_INFO candidate = path;
|
||||
candidate.flags |= DISPLAYCONFIG_PATH_ACTIVE;
|
||||
candidate.sourceInfo.modeInfoIdx =
|
||||
DISPLAYCONFIG_PATH_MODE_IDX_INVALID;
|
||||
candidate.targetInfo.modeInfoIdx =
|
||||
DISPLAYCONFIG_PATH_MODE_IDX_INVALID;
|
||||
|
||||
LONG result = SetDisplayConfig(1, &candidate, 0, NULL,
|
||||
SDC_APPLY | SDC_TOPOLOGY_SUPPLIED | SDC_ALLOW_CHANGES);
|
||||
if (result == ERROR_SUCCESS &&
|
||||
WaitForDisplay(lg, RECOVERY_PATH_ATTEMPTS))
|
||||
{
|
||||
DEBUG_INFO("Activated a saved %s topology",
|
||||
lg ? "Looking Glass" : "non-Looking Glass");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// The connected display may not yet have a database entry. Ask CCD's
|
||||
// best-mode logic for a temporary configuration without saving it.
|
||||
result = SetDisplayConfig(1, &candidate, 0, NULL,
|
||||
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES);
|
||||
if (result == ERROR_SUCCESS &&
|
||||
WaitForDisplay(lg, RECOVERY_PATH_ATTEMPTS))
|
||||
{
|
||||
DEBUG_INFO("Activated a fallback %s display",
|
||||
lg ? "Looking Glass" : "non-Looking Glass");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
lastError = result == ERROR_SUCCESS ?
|
||||
ERROR_NOT_FOUND : static_cast<uint32_t>(result);
|
||||
}
|
||||
|
||||
if (attempted.empty())
|
||||
DEBUG_ERROR("No connected %s display path was found",
|
||||
lg ? "Looking Glass" : "non-Looking Glass");
|
||||
else
|
||||
DEBUG_ERROR("No %s display path could be activated",
|
||||
lg ? "Looking Glass" : "non-Looking Glass");
|
||||
return lastError;
|
||||
}
|
||||
|
||||
uint32_t ActivateFallbackDisplay()
|
||||
{
|
||||
return ActivateDisplay(false);
|
||||
}
|
||||
|
||||
uint32_t RestoreNonExclusiveTopology()
|
||||
{
|
||||
if (HasActiveDisplay(true))
|
||||
return ERROR_SUCCESS;
|
||||
|
||||
LONG result = SetDisplayConfig(0, NULL, 0, NULL,
|
||||
SDC_APPLY | SDC_USE_DATABASE_CURRENT | SDC_ALLOW_CHANGES);
|
||||
if (result == ERROR_SUCCESS && WaitForDisplay(true))
|
||||
{
|
||||
DEBUG_INFO("Saved non-exclusive display topology restored");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
DEBUG_WARN("The saved display topology does not activate Looking Glass");
|
||||
else
|
||||
DEBUG_WARN("Failed to restore the saved display topology (%ld)", result);
|
||||
|
||||
// If the current database topology omits LG, use Windows' most recently
|
||||
// saved clone or extended topology. No persistence flag is supplied, so
|
||||
// this does not replace the user's saved display configuration.
|
||||
result = SetDisplayConfig(0, NULL, 0, NULL,
|
||||
SDC_APPLY | SDC_TOPOLOGY_CLONE | SDC_TOPOLOGY_EXTEND |
|
||||
SDC_ALLOW_CHANGES);
|
||||
if (result == ERROR_SUCCESS && WaitForDisplay(true))
|
||||
{
|
||||
DEBUG_INFO("Non-exclusive Looking Glass topology activated");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
return result == ERROR_SUCCESS ?
|
||||
ERROR_NOT_FOUND : static_cast<uint32_t>(result);
|
||||
}
|
||||
}
|
||||
|
||||
CPipeClient g_pipe;
|
||||
@@ -197,6 +472,20 @@ void CPipeClient::WriteMsg(const LGPipeMsg& msg)
|
||||
m_endpoint.Send(&msg, sizeof(msg));
|
||||
}
|
||||
|
||||
void CPipeClient::OnPipeConnected()
|
||||
{
|
||||
bool hasStatus;
|
||||
LGPipeMsg status;
|
||||
{
|
||||
CSRWSharedLock lock(m_displayLock);
|
||||
hasStatus = m_hasRecoveryStatus;
|
||||
status = m_recoveryStatus;
|
||||
}
|
||||
|
||||
if (hasStatus)
|
||||
WriteMsg(status);
|
||||
}
|
||||
|
||||
void CPipeClient::ReloadSettings()
|
||||
{
|
||||
if (!m_endpoint.IsConnected())
|
||||
@@ -218,6 +507,9 @@ bool CPipeClient::ShouldReconnect()
|
||||
|
||||
bool CPipeClient::EnsureOnlyDisplayLocked()
|
||||
{
|
||||
if (m_recoveryActive)
|
||||
return true;
|
||||
|
||||
std::vector<DisplayState> displays;
|
||||
size_t lgIndex;
|
||||
if (!GetDisplayStates(displays, lgIndex))
|
||||
@@ -322,6 +614,95 @@ bool CPipeClient::EnsureOnlyDisplayLocked()
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t CPipeClient::RestoreSavedTopologyLocked() const
|
||||
{
|
||||
const LONG result = SetDisplayConfig(0, NULL, 0, NULL,
|
||||
SDC_APPLY | SDC_USE_DATABASE_CURRENT | SDC_ALLOW_CHANGES);
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
if (WaitForDisplay(false))
|
||||
{
|
||||
DEBUG_INFO("Recovery display topology activated");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
DEBUG_WARN("The saved topology has no active non-Looking Glass display");
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_WARN("Failed to restore the saved display topology (%ld)", result);
|
||||
}
|
||||
|
||||
return ActivateFallbackDisplay();
|
||||
}
|
||||
|
||||
uint32_t CPipeClient::RestoreLGTopologyLocked()
|
||||
{
|
||||
uint32_t error = ERROR_SUCCESS;
|
||||
bool exclusive = false;
|
||||
|
||||
CRegistrySettings settings;
|
||||
const LSTATUS settingsError = settings.open();
|
||||
if (settingsError != ERROR_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR_HR(settingsError, "Failed to load settings");
|
||||
error = static_cast<uint32_t>(settingsError);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::optional<bool> value = settings.getExclusiveMonitor();
|
||||
if (!value.has_value())
|
||||
error = ERROR_INVALID_DATA;
|
||||
else
|
||||
exclusive = value.value();
|
||||
}
|
||||
|
||||
if (error == ERROR_SUCCESS)
|
||||
{
|
||||
if (!exclusive)
|
||||
{
|
||||
error = RestoreNonExclusiveTopology();
|
||||
if (error == ERROR_SUCCESS)
|
||||
{
|
||||
m_recoveryActive = false;
|
||||
DEBUG_INFO("Looking Glass display topology restored");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!HasActiveDisplay(true))
|
||||
error = ActivateDisplay(true);
|
||||
|
||||
if (error == ERROR_SUCCESS)
|
||||
{
|
||||
// Keep notification-driven display enforcement suppressed until the
|
||||
// LG path is active. This explicit call owns the transition back to
|
||||
// the temporary LG-only topology.
|
||||
m_recoveryActive = false;
|
||||
if (EnsureOnlyDisplayLocked() && WaitForOnlyLGDisplay())
|
||||
{
|
||||
DEBUG_INFO("Looking Glass display topology restored");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
error = ERROR_GEN_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A failed exit must leave a usable recovery display rather than a blank
|
||||
// desktop if a partial CCD transition disabled the physical path.
|
||||
m_recoveryActive = true;
|
||||
if (!HasActiveDisplay(false))
|
||||
{
|
||||
const uint32_t fallbackError = ActivateFallbackDisplay();
|
||||
if (fallbackError != ERROR_SUCCESS)
|
||||
DEBUG_ERROR("Failed to restore a recovery display (%u)", fallbackError);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
bool CPipeClient::EnsureOnlyDisplay()
|
||||
{
|
||||
CSRWExclusiveLock lock(m_displayLock);
|
||||
@@ -355,6 +736,10 @@ bool CPipeClient::OnPipeMessage(const void * message, size_t size)
|
||||
HandleResolutionRejected(msg);
|
||||
return true;
|
||||
|
||||
case LGPipeMsg::SET_RECOVERY:
|
||||
HandleSetRecovery(msg);
|
||||
return true;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unknown message type %d", msg.type);
|
||||
return true;
|
||||
@@ -411,3 +796,70 @@ void CPipeClient::HandleResolutionRejected(const LGPipeMsg& msg)
|
||||
msg.resolutionRejected.height,
|
||||
msg.resolutionRejected.requiredSizeMiB);
|
||||
}
|
||||
|
||||
void CPipeClient::HandleSetRecovery(const LGPipeMsg& msg)
|
||||
{
|
||||
const bool active =
|
||||
(msg.recovery.request & LGPipeMsg::RECOVERY_ACTIVE) != 0;
|
||||
bool cached = false;
|
||||
LGPipeMsg status = {};
|
||||
status.size = sizeof(status);
|
||||
status.type = LGPipeMsg::RECOVERY_FAILED;
|
||||
status.recovery = msg.recovery;
|
||||
|
||||
{
|
||||
CSRWExclusiveLock lock(m_displayLock);
|
||||
cached = m_hasRecoveryStatus &&
|
||||
m_recoveryStatus.recovery.session == msg.recovery.session &&
|
||||
m_recoveryStatus.recovery.request == msg.recovery.request;
|
||||
if (cached)
|
||||
status = m_recoveryStatus;
|
||||
else if (active)
|
||||
{
|
||||
m_recoveryActive = true;
|
||||
CNotifyWindow::instance().setRecoveryMode(true);
|
||||
|
||||
const uint32_t error = RestoreSavedTopologyLocked();
|
||||
if (error == ERROR_SUCCESS)
|
||||
status.type = LGPipeMsg::RECOVERY_ON;
|
||||
else if (error == ERROR_NOT_FOUND)
|
||||
status.type = LGPipeMsg::RECOVERY_NO_DISPLAY;
|
||||
else
|
||||
status.type = LGPipeMsg::RECOVERY_FAILED;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_recoveryActive = true;
|
||||
CNotifyWindow::instance().setRecoveryMode(true);
|
||||
|
||||
if (RestoreLGTopologyLocked() == ERROR_SUCCESS)
|
||||
{
|
||||
CNotifyWindow::instance().setRecoveryMode(false);
|
||||
status.type = LGPipeMsg::RECOVERY_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cached)
|
||||
{
|
||||
m_hasRecoveryStatus = true;
|
||||
m_recoveryStatus = status;
|
||||
}
|
||||
}
|
||||
|
||||
if (cached)
|
||||
{
|
||||
DEBUG_TRACE("Replaying cached recovery status");
|
||||
WriteMsg(status);
|
||||
return;
|
||||
}
|
||||
|
||||
if (active)
|
||||
DEBUG_INFO("Recovery mode %s", status.type == LGPipeMsg::RECOVERY_ON ?
|
||||
"active" : "failed");
|
||||
else if (status.type == LGPipeMsg::RECOVERY_OFF)
|
||||
DEBUG_INFO("Recovery mode disabled");
|
||||
else
|
||||
DEBUG_INFO("Recovery mode exit failed");
|
||||
|
||||
WriteMsg(status);
|
||||
}
|
||||
|
||||
@@ -33,17 +33,25 @@ private:
|
||||
CPipeEndpoint m_endpoint;
|
||||
CSRWLock m_displayLock;
|
||||
|
||||
bool m_recoveryActive = false;
|
||||
bool m_hasRecoveryStatus = false;
|
||||
LGPipeMsg m_recoveryStatus = {};
|
||||
|
||||
void WriteMsg(const LGPipeMsg& msg);
|
||||
|
||||
void SetActiveDesktop();
|
||||
|
||||
bool EnsureOnlyDisplayLocked();
|
||||
|
||||
uint32_t RestoreSavedTopologyLocked() const;
|
||||
uint32_t RestoreLGTopologyLocked();
|
||||
|
||||
void HandleSetCursorPos(const LGPipeMsg& msg);
|
||||
void HandleSetDisplayMode(const LGPipeMsg& msg);
|
||||
void HandleGPUStatus(const LGPipeMsg& msg);
|
||||
void HandleResolutionRejected(const LGPipeMsg& msg);
|
||||
void HandleSetRecovery(const LGPipeMsg& msg);
|
||||
|
||||
void OnPipeConnected() override;
|
||||
bool ShouldReconnect() override;
|
||||
bool OnPipeMessage(const void * message, size_t size) override;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user