Files
LookingGlass/idd/LGIddHelper/CPipeClient.cpp
Geoffrey McRae 82c59cecf2
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
[idd] helper: retry topology after desktop access failure
The interactive Helper can start when the console user token exists but
before WinSta0\Default is ready. SetDisplayConfig then returns
ERROR_ACCESS_DENIED while recovery exit caches the failure and keeps
topology enforcement suppressed.

Keep the recovery request pending while desktop access is transient.
Move the pipe worker to the input desktop and retry without caching
intermediate failures. Leave an exhausted access failure uncached so a
later replay can recover.

Restore the periodic enforcement timer and retry topology without
reapplying a display mode that Windows has already accepted.
2026-08-21 01:02:10 +10:00

1432 lines
40 KiB
C++

/**
* 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 "CPipeClient.h"
#include "CClipboardRing.h"
#include "CDebug.h"
#include "CSRWLock.h"
#include "CNotifyWindow.h"
#include "CRegistrySettings.h"
#include "RefreshRate.h"
#include <setupapi.h>
#include <tchar.h>
#include <vector>
namespace
{
static const unsigned RECOVERY_VERIFY_ATTEMPTS = 20;
static const unsigned RECOVERY_PATH_ATTEMPTS = 20;
static const unsigned RECOVERY_ACCESS_ATTEMPTS = 20;
static const size_t RECOVERY_MAX_PATHS = 4;
static const DWORD RECOVERY_VERIFY_DELAY_MS = 100;
static const DWORD RECOVERY_ACCESS_DELAY_MS = 250;
static const unsigned DISPLAY_MODE_ATTEMPTS = 20;
static const DWORD DISPLAY_MODE_DELAY_MS = 100;
struct DisplayState
{
DISPLAY_DEVICE device;
DEVMODE mode;
bool isLG;
};
bool IsLGDisplay(const DISPLAY_DEVICE& device)
{
static const TCHAR deviceId[] = _T("ROOT\\LGIDD");
const size_t deviceIdLength = _countof(deviceId) - 1;
if (_tcsnicmp(device.DeviceID, deviceId, deviceIdLength) == 0 &&
(device.DeviceID[deviceIdLength] == _T('\\') ||
device.DeviceID[deviceIdLength] == _T('\0')))
return true;
if (_tcsicmp(device.DeviceString,
_T("Looking Glass Indirect Display Device")) == 0)
return true;
DISPLAY_DEVICE monitor = {};
monitor.cb = sizeof(monitor);
for (DWORD i = 0; EnumDisplayDevices(device.DeviceName, i, &monitor, 0); ++i)
{
if (_tcsnicmp(monitor.DeviceID, _T("MONITOR\\LGD1DDD"), 15) == 0 ||
_tcsicmp(monitor.DeviceString, _T("Looking Glass")) == 0)
return true;
monitor = {};
monitor.cb = sizeof(monitor);
}
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 GetDesktopName(HDESK desktop, std::vector<wchar_t>& name)
{
DWORD bytes = 0;
if (GetUserObjectInformationW(
desktop, UOI_NAME, nullptr, 0, &bytes) ||
GetLastError() != ERROR_INSUFFICIENT_BUFFER ||
bytes < sizeof(wchar_t))
return false;
name.resize((bytes + sizeof(wchar_t) - 1) / sizeof(wchar_t));
return GetUserObjectInformationW(desktop, UOI_NAME,
name.data(), static_cast<DWORD>(name.size() * sizeof(wchar_t)),
&bytes) != FALSE;
}
bool SameDesktop(HDESK first, HDESK second)
{
if (!first || !second)
return false;
std::vector<wchar_t> firstName;
std::vector<wchar_t> secondName;
return GetDesktopName(first, firstName) &&
GetDesktopName(second, secondName) &&
_wcsicmp(firstName.data(), secondName.data()) == 0;
}
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;
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))
{
DisplayState state = {};
state.device = device;
state.mode.dmSize = sizeof(state.mode);
state.isLG = IsLGDisplay(device);
if (!EnumDisplaySettingsEx(device.DeviceName, ENUM_CURRENT_SETTINGS,
&state.mode, 0))
{
DEBUG_WARN_HR(GetLastError(),
"Failed to query the current mode for %ls", device.DeviceName);
return false;
}
if (state.isLG && lgIndex == SIZE_MAX)
lgIndex = displays.size();
displays.emplace_back(state);
}
device = {};
device.cb = sizeof(device);
}
return lgIndex != SIZE_MAX;
}
uint32_t DisplayModeRefresh(const LGPipeMsg& msg)
{
return (msg.displayMode.refresh100uHz + LG_REFRESH_RATE_SCALE / 2) /
LG_REFRESH_RATE_SCALE;
}
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(bool logResult = true)
{
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))
{
if (logResult)
DEBUG_INFO("Saved non-exclusive display topology restored");
return ERROR_SUCCESS;
}
if (logResult)
{
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))
{
if (logResult)
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;
bool CPipeClient::Init()
{
DeInit();
if (!IsLGIddDeviceAttached())
{
DEBUG_ERROR("Looking Glass Indirect Display Device not found");
return false;
}
{
CSRWExclusiveLock lock(m_clipboardSetupLock);
if (!PrepareClipboardMappingLocked())
return false;
}
if (!StartDisplayModeThread())
{
CSRWExclusiveLock lock(m_clipboardSetupLock);
ResetClipboardSetupLocked();
return false;
}
m_endpoint.SetHandler(this);
if (m_endpoint.Start(
LG_PIPE_NAME,
CPipeEndpoint::Mode::Client,
sizeof(LGPipeMsg)))
return true;
StopDisplayModeThread();
{
CSRWExclusiveLock lock(m_clipboardSetupLock);
ResetClipboardSetupLocked();
}
return false;
}
void CPipeClient::DeInit()
{
// Stop first so no endpoint callback can race mapping teardown.
m_endpoint.Stop();
StopDisplayModeThread();
CSRWExclusiveLock lock(m_clipboardSetupLock);
ResetClipboardSetupLocked();
}
bool CPipeClient::IsLGIddDeviceAttached()
{
HDEVINFO hDevInfo = SetupDiGetClassDevs(
NULL,
NULL,
NULL,
DIGCF_ALLCLASSES | DIGCF_PRESENT
);
if (hDevInfo == INVALID_HANDLE_VALUE)
return false;
SP_DEVINFO_DATA DeviceInfoData;
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
bool found = false;
for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData); i++)
{
DWORD DataT;
TCHAR buffer[1024];
DWORD buffersize = 0;
if (!SetupDiGetDeviceRegistryProperty(
hDevInfo,
&DeviceInfoData,
SPDRP_HARDWAREID,
&DataT,
(PBYTE)buffer,
sizeof(buffer),
&buffersize))
continue;
for (LPCTSTR p = buffer; *p; p += _tcslen(p) + 1)
if (_tcsicmp(p, _T("Root\\LGIdd")) == 0)
{
found = true;
break;
}
if (found)
break;
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return found;
}
/* APIs like SetCursorPos are applied to the desktop our thread is
* attached to. If the user switches to the secure desktop (UAC, etc)
* then these functions will not work, so call this first to ensure
* the call is effective */
bool CPipeClient::SetActiveDesktop(bool quiet)
{
// CloseDesktop rejects the handle assigned to this thread. Retain a handle
// acquired for a switch until a later switch makes it safe to close.
static thread_local HDESK ownedDesktop = NULL;
HDESK inputDesktop = OpenInputDesktop(0, FALSE, GENERIC_READ);
if (!inputDesktop)
{
if (!quiet)
DEBUG_ERROR_HR(GetLastError(), "OpenInputDesktop Failed");
return false;
}
const HDESK currentDesktop = GetThreadDesktop(GetCurrentThreadId());
if (SameDesktop(currentDesktop, inputDesktop))
{
if (!CloseDesktop(inputDesktop))
DEBUG_ERROR_HR(GetLastError(), "CloseDesktop Failed");
return true;
}
if (!SetThreadDesktop(inputDesktop))
{
if (!quiet)
DEBUG_ERROR_HR(GetLastError(), "SetThreadDesktop Failed");
if (!CloseDesktop(inputDesktop))
DEBUG_ERROR_HR(GetLastError(), "CloseDesktop Failed");
return false;
}
const HDESK previousOwned = ownedDesktop;
ownedDesktop = inputDesktop;
if (previousOwned && !CloseDesktop(previousOwned))
DEBUG_ERROR_HR(GetLastError(), "CloseDesktop Failed");
return true;
}
void CPipeClient::WriteMsg(const LGPipeMsg& msg)
{
m_endpoint.Send(&msg, sizeof(msg));
}
DWORD WINAPI CPipeClient::DisplayModeThreadProc(void * context)
{
static_cast<CPipeClient *>(context)->DisplayModeThread();
return 0;
}
bool CPipeClient::StartDisplayModeThread()
{
m_displayModeStop = CreateEventW(nullptr, TRUE, FALSE, nullptr);
m_displayModeWake = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (!m_displayModeStop || !m_displayModeWake)
{
DEBUG_ERROR_HR(GetLastError(), "Failed to create display mode events");
StopDisplayModeThread();
return false;
}
m_displayModeThread = CreateThread(
nullptr, 0, DisplayModeThreadProc, this, 0, nullptr);
if (!m_displayModeThread)
{
DEBUG_ERROR_HR(GetLastError(), "Failed to create display mode worker");
StopDisplayModeThread();
return false;
}
return true;
}
void CPipeClient::StopDisplayModeThread()
{
if (m_displayModeStop)
SetEvent(m_displayModeStop);
if (m_displayModeWake)
SetEvent(m_displayModeWake);
if (m_displayModeThread)
{
WaitForSingleObject(m_displayModeThread, INFINITE);
CloseHandle(m_displayModeThread);
}
if (m_displayModeWake)
CloseHandle(m_displayModeWake);
if (m_displayModeStop)
CloseHandle(m_displayModeStop);
m_displayModeThread = nullptr;
m_displayModeWake = nullptr;
m_displayModeStop = nullptr;
CSRWExclusiveLock lock(m_displayModeLock);
m_displayMode = {};
m_displayModeSerial = 0;
m_hasDisplayMode = false;
}
bool CPipeClient::ApplyDisplayMode(const LGPipeMsg& msg, LONG& result)
{
CSRWExclusiveLock lock(m_displayLock);
std::vector<DisplayState> displays;
size_t lgIndex;
if (!GetDisplayStates(displays, lgIndex))
{
result = DISP_CHANGE_FAILED;
return false;
}
// Preserve the original mode-application transaction: Windows accepts the
// requested values before EnumDisplaySettingsEx necessarily lists the new
// mode after a monitor replug.
DEVMODE mode = displays[lgIndex].mode;
mode.dmPelsWidth = msg.displayMode.width;
mode.dmPelsHeight = msg.displayMode.height;
mode.dmDisplayFrequency = DisplayModeRefresh(msg);
mode.dmFields =
DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
result = ChangeDisplaySettingsEx(displays[lgIndex].device.DeviceName,
&mode, NULL, CDS_UPDATEREGISTRY, NULL);
return result == DISP_CHANGE_SUCCESSFUL;
}
void CPipeClient::DisplayModeThread()
{
const HANDLE handles[] = { m_displayModeStop, m_displayModeWake };
uint64_t serial = 0;
unsigned int attempts = 0;
bool applied = false;
bool retryPending = false;
while (true)
{
const DWORD wait = WaitForMultipleObjects(_countof(handles), handles,
FALSE, retryPending ? DISPLAY_MODE_DELAY_MS : INFINITE);
if (wait == WAIT_OBJECT_0)
break;
if (wait == WAIT_FAILED)
{
DEBUG_ERROR_HR(GetLastError(), "Display mode worker wait failed");
break;
}
LGPipeMsg msg = {};
{
CSRWSharedLock lock(m_displayModeLock);
if (!m_hasDisplayMode)
{
attempts = 0;
applied = false;
retryPending = false;
continue;
}
if (serial != m_displayModeSerial)
{
serial = m_displayModeSerial;
attempts = 0;
applied = false;
}
msg = m_displayMode;
}
if (!applied)
{
LONG result = DISP_CHANGE_FAILED;
if (!ApplyDisplayMode(msg, result))
{
++attempts;
if (attempts < DISPLAY_MODE_ATTEMPTS)
{
retryPending = true;
continue;
}
CSRWExclusiveLock lock(m_displayModeLock);
if (serial == m_displayModeSerial)
{
DEBUG_ERROR("Failed to apply Looking Glass display mode %ux%u@%u (%ld)",
msg.displayMode.width,
msg.displayMode.height,
DisplayModeRefresh(msg),
result);
m_hasDisplayMode = false;
}
attempts = 0;
applied = false;
retryPending = false;
continue;
}
// ChangeDisplaySettingsEx returning success starts an asynchronous
// transition. Do not restart it on the next retry.
applied = true;
attempts = 0;
retryPending = true;
continue;
}
if (EnsureOnlyDisplay())
{
CSRWExclusiveLock lock(m_displayModeLock);
if (serial == m_displayModeSerial)
m_hasDisplayMode = false;
attempts = 0;
applied = false;
retryPending = false;
continue;
}
++attempts;
if (attempts < DISPLAY_MODE_ATTEMPTS)
{
retryPending = true;
continue;
}
CSRWExclusiveLock lock(m_displayModeLock);
if (serial == m_displayModeSerial)
{
DEBUG_ERROR(
"Failed to enforce the Looking Glass-only topology after applying "
"display mode %ux%u@%u",
msg.displayMode.width,
msg.displayMode.height,
DisplayModeRefresh(msg));
m_hasDisplayMode = false;
}
attempts = 0;
applied = false;
retryPending = false;
}
}
bool CPipeClient::PipeServerIsAuthorized(HANDLE pipe)
{
DWORD session = 0xFFFFFFFFU;
if (!GetNamedPipeServerSessionId(pipe, &session))
{
const DWORD error = GetLastError();
DEBUG_WARN_HR(error, "Failed to identify named pipe server session");
return false;
}
if (session != 0)
{
DEBUG_WARN(
"Rejected named pipe server outside the service session");
return false;
}
return true;
}
bool CPipeClient::BuildPipeClientHello(void * message, size_t size)
{
CSRWSharedLock setupLock(m_clipboardSetupLock);
if (!message || size != sizeof(LGPipeMsg) ||
!m_clipboardMapping || !m_clipboardEpoch)
{
DEBUG_ERROR("Clipboard mapping was not prepared before pipe connection");
return false;
}
LGPipeMsg& hello = *static_cast<LGPipeMsg *>(message);
hello = {};
hello.size = sizeof(hello);
hello.type = LGPipeMsg::HELLO;
hello.hello.version = LGPipeMsg::PROTOCOL_VERSION;
hello.hello.authorityId[0] = m_clipboardAuthorityId[0];
hello.hello.authorityId[1] = m_clipboardAuthorityId[1];
return true;
}
void CPipeClient::OnPipeConnected()
{
bool hasStatus;
LGPipeMsg status;
{
CSRWSharedLock lock(m_displayLock);
hasStatus = m_hasRecoveryStatus;
status = m_recoveryStatus;
}
if (hasStatus)
WriteMsg(status);
}
void CPipeClient::OnPipeDisconnected()
{
CSRWExclusiveLock lock(m_clipboardSetupLock);
ResetClipboardSetupLocked();
}
void CPipeClient::ReloadSettings()
{
if (!m_endpoint.IsConnected())
return;
LGPipeMsg msg = {};
msg.size = sizeof(msg);
msg.type = LGPipeMsg::RELOADSETTINGS;
WriteMsg(msg);
}
bool CPipeClient::ShouldReconnect()
{
const bool attached = IsLGIddDeviceAttached();
if (!attached)
DEBUG_INFO("Looking Glass Indirect Display Device was removed");
else
{
CSRWExclusiveLock lock(m_clipboardSetupLock);
if (!m_clipboardMapping && !PrepareClipboardMappingLocked())
DEBUG_WARN("Clipboard mapping is not ready for reconnection");
}
return attached;
}
bool CPipeClient::EnsureOnlyDisplayLocked(uint32_t * error, bool logResult)
{
if (error)
*error = ERROR_SUCCESS;
if (m_recoveryActive)
return true;
std::vector<DisplayState> displays;
size_t lgIndex;
if (!GetDisplayStates(displays, lgIndex))
{
if (error)
*error = ERROR_NOT_FOUND;
return false;
}
// The only active display is necessarily the primary display.
if (displays.size() == 1)
return true;
for (unsigned int attempt = 0; attempt < 3; ++attempt)
{
UINT32 pathCount = 0;
UINT32 modeCount = 0;
LONG result = GetDisplayConfigBufferSizes(
QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount);
if (result != ERROR_SUCCESS)
{
if (error)
*error = static_cast<uint32_t>(result);
if (logResult)
DEBUG_ERROR("GetDisplayConfigBufferSizes failed (%ld)", result);
return false;
}
std::vector<DISPLAYCONFIG_PATH_INFO> paths(pathCount);
std::vector<DISPLAYCONFIG_MODE_INFO> modes(modeCount);
result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS,
&pathCount, paths.data(), &modeCount, modes.data(), NULL);
if (result == ERROR_INSUFFICIENT_BUFFER)
continue;
if (result != ERROR_SUCCESS)
{
if (error)
*error = static_cast<uint32_t>(result);
if (logResult)
DEBUG_ERROR("QueryDisplayConfig failed (%ld)", result);
return false;
}
paths.resize(pathCount);
modes.resize(modeCount);
for (size_t i = 0; i < paths.size(); ++i)
{
DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName = {};
sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
sourceName.header.size = sizeof(sourceName);
sourceName.header.adapterId = paths[i].sourceInfo.adapterId;
sourceName.header.id = paths[i].sourceInfo.id;
result = DisplayConfigGetDeviceInfo(&sourceName.header);
if (result != ERROR_SUCCESS)
continue;
if (_tcsicmp(sourceName.viewGdiDeviceName,
displays[lgIndex].device.DeviceName) != 0)
continue;
const UINT32 sourceModeIndex = paths[i].sourceInfo.modeInfoIdx;
const UINT32 targetModeIndex = paths[i].targetInfo.modeInfoIdx;
if (sourceModeIndex == DISPLAYCONFIG_PATH_MODE_IDX_INVALID ||
sourceModeIndex >= modes.size() ||
targetModeIndex == DISPLAYCONFIG_PATH_MODE_IDX_INVALID ||
targetModeIndex >= modes.size() ||
modes[sourceModeIndex].infoType != DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE ||
modes[targetModeIndex].infoType != DISPLAYCONFIG_MODE_INFO_TYPE_TARGET)
{
if (error)
*error = ERROR_INVALID_DATA;
if (logResult)
DEBUG_ERROR("Looking Glass display path has invalid mode indices");
return false;
}
DISPLAYCONFIG_PATH_INFO path = paths[i];
DISPLAYCONFIG_MODE_INFO selectedModes[2] = {
modes[sourceModeIndex], modes[targetModeIndex]
};
selectedModes[0].sourceMode.position.x = 0;
selectedModes[0].sourceMode.position.y = 0;
path.sourceInfo.modeInfoIdx = 0;
path.targetInfo.modeInfoIdx = 1;
path.flags |= DISPLAYCONFIG_PATH_ACTIVE;
// Keep a bootable physical-display topology in the persistence
// database. Persisting an LG-only topology creates a dependency cycle
// on Windows 10 at the next boot: IddCx waits for display topology
// initialization while the saved topology waits for this IDD adapter.
// The helper reapplies this temporary topology on startup, display
// changes and periodically, so it remains enforced for the session.
result = SetDisplayConfig(1, &path, 2, selectedModes,
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES);
if (result != ERROR_SUCCESS)
{
if (error)
*error = static_cast<uint32_t>(result);
if (logResult)
DEBUG_ERROR("Failed to apply the LG-only display topology (%ld)",
result);
return false;
}
if (logResult)
DEBUG_INFO("Looking Glass display set as the only active display");
return true;
}
if (error)
*error = ERROR_NOT_FOUND;
if (logResult)
DEBUG_ERROR("Looking Glass display configuration path not found");
return false;
}
if (error)
*error = ERROR_RETRY;
if (logResult)
DEBUG_WARN("Display topology kept changing while selecting Looking Glass");
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(bool logResult)
{
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(logResult);
if (error == ERROR_SUCCESS)
{
m_recoveryActive = false;
if (logResult)
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;
uint32_t ensureError = ERROR_GEN_FAILURE;
if (EnsureOnlyDisplayLocked(&ensureError, logResult) &&
WaitForOnlyLGDisplay())
{
if (logResult)
DEBUG_INFO("Looking Glass display topology restored");
return ERROR_SUCCESS;
}
error = ensureError == ERROR_SUCCESS ?
ERROR_GEN_FAILURE : ensureError;
}
}
}
// 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);
return EnsureOnlyDisplayLocked();
}
bool CPipeClient::OnPipeMessage(const void * message, size_t size)
{
if (size != sizeof(LGPipeMsg))
return false;
const LGPipeMsg & msg = *static_cast<const LGPipeMsg *>(message);
if (msg.size != sizeof(msg))
return false;
switch (msg.type)
{
case LGPipeMsg::SETCURSORPOS:
HandleSetCursorPos(msg);
return true;
case LGPipeMsg::SETDISPLAYMODE:
HandleSetDisplayMode(msg);
return true;
case LGPipeMsg::GPUSTATUS:
HandleGPUStatus(msg);
return true;
case LGPipeMsg::RESOLUTIONREJECTED:
HandleResolutionRejected(msg);
return true;
case LGPipeMsg::SET_RECOVERY:
HandleSetRecovery(msg);
return true;
case LGPipeMsg::CLIPBOARD_READY:
{
CSRWExclusiveLock setupLock(m_clipboardSetupLock);
if (msg.clipboardReady.status != ERROR_SUCCESS ||
msg.clipboardReady.epoch != m_clipboardEpoch)
{
DEBUG_WARN("IDD rejected the clipboard mapping (%u)",
msg.clipboardReady.status);
ResetClipboardSetupLocked();
return true;
}
if (!m_clipboardEnabled)
{
const uint64_t epoch = m_clipboardEpoch;
ResetClipboardSetupLocked();
setupLock.Unlock();
LGPipeMsg failure = {};
failure.size = sizeof(failure);
failure.type = LGPipeMsg::CLIPBOARD_READY;
failure.clipboardReady.epoch = epoch;
failure.clipboardReady.status = ERROR_NOT_SUPPORTED;
m_endpoint.Send(&failure, sizeof(failure));
return true;
}
HANDLE mapping = nullptr;
DWORD failureStatus = ERROR_SUCCESS;
if (!m_clipboardMapping)
{
failureStatus = ERROR_NOT_READY;
DEBUG_ERROR_HR(failureStatus,
"Service-owned clipboard mapping is unavailable");
}
else if (!DuplicateHandle(GetCurrentProcess(), m_clipboardMapping,
GetCurrentProcess(), &mapping, 0, FALSE, DUPLICATE_SAME_ACCESS))
{
failureStatus = GetLastError();
DEBUG_ERROR_HR(failureStatus,
"Failed to duplicate the service-owned clipboard mapping");
}
else if (!m_clipboard.Attach(
mapping, m_clipboardEpoch, true, *this))
{
failureStatus = ERROR_INVALID_DATA;
DEBUG_ERROR_HR(failureStatus,
"Failed to activate the service-owned clipboard mapping");
}
if (failureStatus != ERROR_SUCCESS)
{
const uint64_t epoch = m_clipboardEpoch;
ResetClipboardSetupLocked();
setupLock.Unlock();
// Tell the IDD that its successful mapping is unusable here, so it
// does not leave a one-sided channel advertised as available.
LGPipeMsg failure = {};
failure.size = sizeof(failure);
failure.type = LGPipeMsg::CLIPBOARD_READY;
failure.clipboardReady.epoch = epoch;
failure.clipboardReady.status = failureStatus;
m_endpoint.Send(&failure, sizeof(failure));
}
return true;
}
case LGPipeMsg::CLIPBOARD_KICK:
m_clipboard.Kick(msg.clipboardKick.epoch);
return true;
case LGPipeMsg::CLIPBOARD_RESET:
m_clipboard.Reset(
msg.clipboardReset.epoch, msg.clipboardReset.reason);
return true;
default:
DEBUG_ERROR("Unknown message type %d", msg.type);
return true;
}
}
bool CPipeClient::ClipboardKick(uint64_t epoch)
{
LGPipeMsg msg = {};
msg.size = sizeof(msg);
msg.type = LGPipeMsg::CLIPBOARD_KICK;
msg.clipboardKick.epoch = epoch;
return m_endpoint.Send(&msg, sizeof(msg));
}
void CPipeClient::ClipboardResetPeer(uint64_t epoch, uint32_t reason)
{
LGPipeMsg msg = {};
msg.size = sizeof(msg);
msg.type = LGPipeMsg::CLIPBOARD_RESET;
msg.clipboardReset.epoch = epoch;
msg.clipboardReset.reason = reason;
m_endpoint.Send(&msg, sizeof(msg));
}
bool CPipeClient::PrepareClipboardMappingLocked()
{
if (m_clipboardMapping)
return true;
if (!m_clipboardMappingId[0] || !m_clipboardMappingId[1])
{
DEBUG_ERROR("Invalid service-owned clipboard mapping identifier");
return false;
}
wchar_t mappingName[128];
const int nameResult = _snwprintf_s(
mappingName, _countof(mappingName), _TRUNCATE,
L"Global\\LookingGlassIDDClipboard-%016llx%016llx",
static_cast<unsigned long long>(m_clipboardMappingId[0]),
static_cast<unsigned long long>(m_clipboardMappingId[1]));
if (nameResult < 0)
{
DEBUG_ERROR_HR(ERROR_INSUFFICIENT_BUFFER,
"Failed to format service-owned clipboard mapping name");
return false;
}
m_clipboardMapping = OpenFileMappingW(
FILE_MAP_READ | FILE_MAP_WRITE, FALSE, mappingName);
if (!m_clipboardMapping)
{
const DWORD error = GetLastError();
DEBUG_ERROR_HR(error,
"Failed to open service-owned clipboard mapping");
return false;
}
ClipboardMapping * view = static_cast<ClipboardMapping *>(MapViewOfFile(
m_clipboardMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0,
sizeof(ClipboardMapping)));
if (!view)
{
const DWORD error = GetLastError();
DEBUG_ERROR_HR(error,
"Failed to initialize service-owned clipboard mapping");
ResetClipboardSetupLocked();
return false;
}
m_clipboardAuthorityId[0] = view->authorityId[0];
m_clipboardAuthorityId[1] = view->authorityId[1];
if (!m_clipboardAuthorityId[0] || !m_clipboardAuthorityId[1])
{
DEBUG_ERROR("Invalid clipboard authority identifier");
UnmapViewOfFile(view);
ResetClipboardSetupLocked();
return false;
}
++m_clipboardEpochCounter;
if (!m_clipboardEpochCounter)
++m_clipboardEpochCounter;
m_clipboardEpoch = m_clipboardEpochCounter;
CClipboardRing::Initialize(
*view, m_clipboardEpoch, m_clipboardAuthorityId);
if (!UnmapViewOfFile(view))
{
const DWORD error = GetLastError();
DEBUG_ERROR_HR(error,
"Failed to unmap initialized service-owned clipboard mapping");
ResetClipboardSetupLocked();
return false;
}
return true;
}
void CPipeClient::ResetClipboardSetupLocked()
{
m_clipboard.Detach();
if (m_clipboardMapping)
CloseHandle(m_clipboardMapping);
m_clipboardMapping = nullptr;
m_clipboardEpoch = 0;
m_clipboardAuthorityId[0] = 0;
m_clipboardAuthorityId[1] = 0;
}
void CPipeClient::HandleSetCursorPos(const LGPipeMsg& msg)
{
SetActiveDesktop();
SetCursorPos(msg.curorPos.x, msg.curorPos.y);
}
void CPipeClient::HandleSetDisplayMode(const LGPipeMsg& msg)
{
// The IDD reaches swap-chain readiness while the replugged monitor can
// still be settling. Latch the latest request and retry until Windows
// accepts it; after acceptance, retry only topology enforcement.
{
CSRWExclusiveLock lock(m_displayModeLock);
m_displayMode = msg;
++m_displayModeSerial;
m_hasDisplayMode = true;
}
SetEvent(m_displayModeWake);
}
void CPipeClient::HandleGPUStatus(const LGPipeMsg& msg)
{
CNotifyWindow::instance().setGPU(!msg.gpuStatus.software);
}
void CPipeClient::HandleResolutionRejected(const LGPipeMsg& msg)
{
CNotifyWindow::instance().notifyResolutionRejected(
msg.resolutionRejected.width,
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;
bool recoveryExitAccessUnavailable = false;
unsigned int recoveryExitAttempts = 0;
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);
// This callback runs on CPipeEndpoint's worker, which owns no windows
// or hooks and can safely follow the input desktop. During logon the
// user token can become available just before that desktop is ready;
// keep this request pending rather than caching a transient failure.
SetActiveDesktop(true);
recoveryExitAttempts = 1;
uint32_t error = RestoreLGTopologyLocked();
while (error == ERROR_ACCESS_DENIED &&
recoveryExitAttempts < RECOVERY_ACCESS_ATTEMPTS)
{
Sleep(RECOVERY_ACCESS_DELAY_MS);
++recoveryExitAttempts;
if (!SetActiveDesktop(true))
continue;
error = RestoreLGTopologyLocked(false);
}
recoveryExitAccessUnavailable = error == ERROR_ACCESS_DENIED;
if (error == ERROR_SUCCESS)
{
CNotifyWindow::instance().setRecoveryMode(false);
status.type = LGPipeMsg::RECOVERY_OFF;
}
}
// Do not make a transient desktop-access failure sticky. Report this
// attempt, but let an identical OFF replay try again after reconnect.
if (!cached && !recoveryExitAccessUnavailable)
{
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)
{
if (recoveryExitAttempts > 1)
DEBUG_INFO("Recovery mode disabled after %u desktop access attempts",
recoveryExitAttempts);
else
DEBUG_INFO("Recovery mode disabled");
}
else if (recoveryExitAccessUnavailable)
DEBUG_WARN(
"Recovery mode exit failed: display configuration access remained "
"unavailable after %u attempts",
recoveryExitAttempts);
else
DEBUG_INFO("Recovery mode exit failed");
WriteMsg(status);
}