1
0
Fork 0
libreoffice/vcl/source/opengl/win/WinDeviceInfo.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

491 lines
18 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <opengl/win/WinDeviceInfo.hxx>
#include <driverblocklist.hxx>
#include <config_folders.h>
#if !defined WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <objbase.h>
#include <setupapi.h>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string_view>
#include <osl/file.hxx>
#include <rtl/bootstrap.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <tools/stream.hxx>
#include <o3tl/char16_t2wchar_t.hxx>
#include <desktop/crashreport.hxx>
namespace {
bool GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, OUString& destString, int type)
{
HKEY key;
DWORD dwcbData;
DWORD dValue;
DWORD resultType;
LONG result;
bool retval = true;
result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key);
if (result != ERROR_SUCCESS)
{
return false;
}
switch (type)
{
case REG_DWORD:
{
// We only use this for vram size
dwcbData = sizeof(dValue);
result = RegQueryValueExW(key, keyName, nullptr, &resultType,
reinterpret_cast<LPBYTE>(&dValue), &dwcbData);
if (result == ERROR_SUCCESS && resultType == REG_DWORD)
{
dValue = dValue / 1024 / 1024;
destString += OUString::number(int32_t(dValue));
}
else
{
retval = false;
}
break;
}
case REG_MULTI_SZ:
{
// A chain of null-separated strings; we convert the nulls to spaces
WCHAR wCharValue[1024];
dwcbData = sizeof(wCharValue);
result = RegQueryValueExW(key, keyName, nullptr, &resultType,
reinterpret_cast<LPBYTE>(wCharValue), &dwcbData);
if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ)
{
// This bit here could probably be cleaner.
bool isValid = false;
DWORD strLen = dwcbData/sizeof(wCharValue[0]);
for (DWORD i = 0; i < strLen; i++)
{
if (wCharValue[i] == '\0')
{
if (i < strLen - 1 && wCharValue[i + 1] == '\0')
{
isValid = true;
break;
}
else
{
wCharValue[i] = ' ';
}
}
}
// ensure wCharValue is null terminated
wCharValue[strLen-1] = '\0';
if (isValid)
destString = OUString(o3tl::toU(wCharValue));
}
else
{
retval = false;
}
break;
}
}
RegCloseKey(key);
return retval;
}
// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD
// this function is used to extract the id's out of it
uint32_t ParseIDFromDeviceID(const OUString &key, const char *prefix, int length)
{
OUString id = key.toAsciiUpperCase();
OUString aPrefix = OUString::fromUtf8(prefix);
int32_t start = id.indexOf(aPrefix);
if (start != -1)
{
id = id.copy(start + aPrefix.getLength(), length);
}
return id.toUInt32(16);
}
/* Other interesting places for info:
* IDXGIAdapter::GetDesc()
* IDirectDraw7::GetAvailableVidMem()
* e->GetAvailableTextureMem()
* */
template<typename T> void appendIntegerWithPadding(OUString& rString, T value, sal_uInt32 nChars)
{
rString += "0x";
OUString aValue = OUString::number(value, 16);
sal_Int32 nLength = aValue.getLength();
sal_uInt32 nPadLength = nChars - nLength;
assert(nPadLength >= 0);
OUStringBuffer aBuffer;
for (sal_uInt32 i = 0; i < nPadLength; ++i)
{
aBuffer.append("0");
}
rString += aBuffer.makeStringAndClear() + aValue;
}
#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\"
}
WinOpenGLDeviceInfo::WinOpenGLDeviceInfo():
mbHasDualGPU(false),
mbRDP(false)
{
GetData();
}
static OUString getDenylistFile()
{
OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
rtl::Bootstrap::expandMacros(url);
return url + "/opengl/opengl_denylist_windows.xml";
}
bool WinOpenGLDeviceInfo::FindBlocklistedDeviceInList()
{
return DriverBlocklist::IsDeviceBlocked( getDenylistFile(), DriverBlocklist::VersionType::OpenGL,
maDriverVersion, maAdapterVendorID, maAdapterDeviceID);
}
namespace {
OUString getCacheFolder()
{
OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
rtl::Bootstrap::expandMacros(url);
osl::Directory::create(url);
return url;
}
void writeToLog(SvStream& rStrm, const char* pKey, std::u16string_view rVal)
{
rStrm.WriteOString(pKey);
rStrm.WriteOString(": ");
rStrm.WriteOString(OUStringToOString(rVal, RTL_TEXTENCODING_UTF8));
rStrm.WriteChar('\n');
}
}
bool WinOpenGLDeviceInfo::isDeviceBlocked()
{
CrashReporter::addKeyValue("OpenGLVendor", maAdapterVendorID, CrashReporter::AddItem);
CrashReporter::addKeyValue("OpenGLDevice", maAdapterDeviceID, CrashReporter::AddItem);
CrashReporter::addKeyValue("OpenGLDriver", maDriverVersion, CrashReporter::Write);
SAL_INFO("vcl.opengl", maDriverVersion);
SAL_INFO("vcl.opengl", maDriverDate);
SAL_INFO("vcl.opengl", maDeviceID);
SAL_INFO("vcl.opengl", maAdapterVendorID);
SAL_INFO("vcl.opengl", maAdapterDeviceID);
SAL_INFO("vcl.opengl", maAdapterSubsysID);
SAL_INFO("vcl.opengl", maDeviceKey);
SAL_INFO("vcl.opengl", maDeviceString);
OUString aCacheFolder = getCacheFolder();
OUString aCacheFile(aCacheFolder + "/opengl_device.log");
SvFileStream aOpenGLLogFile(aCacheFile, StreamMode::WRITE|StreamMode::TRUNC);
writeToLog(aOpenGLLogFile, "DriverVersion", maDriverVersion);
writeToLog(aOpenGLLogFile, "DriverDate", maDriverDate);
writeToLog(aOpenGLLogFile, "DeviceID", maDeviceID);
writeToLog(aOpenGLLogFile, "AdapterVendorID", maAdapterVendorID);
writeToLog(aOpenGLLogFile, "AdapterDeviceID", maAdapterDeviceID);
writeToLog(aOpenGLLogFile, "AdapterSubsysID", maAdapterSubsysID);
writeToLog(aOpenGLLogFile, "DeviceKey", maDeviceKey);
writeToLog(aOpenGLLogFile, "DeviceString", maDeviceString);
// Check if the device is blocked from the downloaded blocklist. If not, check
// the static list after that. This order is used so that we can later escape
// out of static blocks (i.e. if we were wrong or something was patched, we
// can back out our static block without doing a release).
if (mbRDP)
{
SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions");
return true;
}
return FindBlocklistedDeviceInList();
}
void WinOpenGLDeviceInfo::GetData()
{
DISPLAY_DEVICEW displayDevice;
displayDevice.cb = sizeof(displayDevice);
int deviceIndex = 0;
while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0))
{
if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
{
break;
}
deviceIndex++;
}
// make sure the string is null terminated
// (using the term "null" here to mean a zero UTF-16 unit)
if (wcsnlen(displayDevice.DeviceKey, SAL_N_ELEMENTS(displayDevice.DeviceKey))
== SAL_N_ELEMENTS(displayDevice.DeviceKey))
{
// we did not find a null
SAL_WARN("vcl.opengl", "string not null terminated");
return;
}
/* DeviceKey is "reserved" according to MSDN so we'll be careful with it */
/* check that DeviceKey begins with DEVICE_KEY_PREFIX */
/* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insensitively */
if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1) != 0)
{
SAL_WARN("vcl.opengl", "incorrect DeviceKey");
return;
}
// chop off DEVICE_KEY_PREFIX
maDeviceKey = o3tl::toU(displayDevice.DeviceKey) + SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1;
maDeviceID = o3tl::toU(displayDevice.DeviceID);
maDeviceString = o3tl::toU(displayDevice.DeviceString);
if (maDeviceID.isEmpty() &&
(maDeviceString == "RDPDD Chained DD" ||
(maDeviceString == "RDPUDD Chained DD")))
{
// we need to block RDP as it does not provide OpenGL 2.1+
mbRDP = true;
SAL_WARN("vcl.opengl", "RDP => blocked");
return;
}
/* create a device information set composed of the current display device */
HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, o3tl::toW(maDeviceID.getStr()), nullptr,
DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
if (devinfo != INVALID_HANDLE_VALUE)
{
HKEY key;
LONG result;
WCHAR value[255];
DWORD dwcbData;
SP_DEVINFO_DATA devinfoData;
DWORD memberIndex = 0;
devinfoData.cbSize = sizeof(devinfoData);
/* enumerate device information elements in the device information set */
while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
{
/* get a string that identifies the device's driver key */
if (SetupDiGetDeviceRegistryPropertyW(devinfo,
&devinfoData,
SPDRP_DRIVER,
nullptr,
reinterpret_cast<PBYTE>(value),
sizeof(value),
nullptr))
{
OUString driverKey(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey.getStr()), 0, KEY_QUERY_VALUE, &key);
if (result == ERROR_SUCCESS)
{
/* we've found the driver we're looking for */
dwcbData = sizeof(value);
result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
reinterpret_cast<LPBYTE>(value), &dwcbData);
if (result == ERROR_SUCCESS)
{
maDriverVersion = OUString(o3tl::toU(value));
}
else
{
// If the entry wasn't found, assume the worst (0.0.0.0).
maDriverVersion = OUString("0.0.0.0");
}
dwcbData = sizeof(value);
result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
reinterpret_cast<LPBYTE>(value), &dwcbData);
if (result == ERROR_SUCCESS)
{
maDriverDate = o3tl::toU(value);
}
else
{
// Again, assume the worst
maDriverDate = OUString("01-01-1970");
}
RegCloseKey(key);
break;
}
}
}
SetupDiDestroyDeviceInfoList(devinfo);
}
else
{
SAL_WARN("vcl.opengl", "invalid handle value");
}
appendIntegerWithPadding(maAdapterVendorID, ParseIDFromDeviceID(maDeviceID, "VEN_", 4), 4);
appendIntegerWithPadding(maAdapterDeviceID, ParseIDFromDeviceID(maDeviceID, "&DEV_", 4), 4);
appendIntegerWithPadding(maAdapterSubsysID, ParseIDFromDeviceID(maDeviceID, "&SUBSYS_", 8), 8);
// We now check for second display adapter.
// Device interface class for display adapters.
CLSID GUID_DISPLAY_DEVICE_ARRIVAL;
HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}",
&GUID_DISPLAY_DEVICE_ARRIVAL);
if (hresult == NOERROR)
{
devinfo = SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL,
nullptr, nullptr,
DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if (devinfo != INVALID_HANDLE_VALUE)
{
HKEY key;
LONG result;
WCHAR value[255];
DWORD dwcbData;
SP_DEVINFO_DATA devinfoData;
DWORD memberIndex = 0;
devinfoData.cbSize = sizeof(devinfoData);
OUString aAdapterDriver2;
OUString aDeviceID2;
OUString aDriverVersion2;
OUString aDriverDate2;
uint32_t adapterVendorID2;
uint32_t adapterDeviceID2;
/* enumerate device information elements in the device information set */
while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
{
/* get a string that identifies the device's driver key */
if (SetupDiGetDeviceRegistryPropertyW(devinfo,
&devinfoData,
SPDRP_DRIVER,
nullptr,
reinterpret_cast<PBYTE>(value),
sizeof(value),
nullptr))
{
OUString driverKey2(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey2.getStr()), 0, KEY_QUERY_VALUE, &key);
if (result == ERROR_SUCCESS)
{
dwcbData = sizeof(value);
result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr,
nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
if (result != ERROR_SUCCESS)
{
continue;
}
aDeviceID2 = o3tl::toU(value);
OUString aAdapterVendorID2String;
OUString aAdapterDeviceID2String;
adapterVendorID2 = ParseIDFromDeviceID(aDeviceID2, "VEN_", 4);
appendIntegerWithPadding(aAdapterVendorID2String, adapterVendorID2, 4);
adapterDeviceID2 = ParseIDFromDeviceID(aDeviceID2, "&DEV_", 4);
appendIntegerWithPadding(aAdapterDeviceID2String, adapterDeviceID2, 4);
if (maAdapterVendorID == aAdapterVendorID2String &&
maAdapterDeviceID == aAdapterDeviceID2String)
{
RegCloseKey(key);
continue;
}
// If this device is missing driver information, it is unlikely to
// be a real display adapter.
if (!GetKeyValue(o3tl::toW(driverKey2.getStr()), L"InstalledDisplayDrivers",
aAdapterDriver2, REG_MULTI_SZ))
{
RegCloseKey(key);
continue;
}
dwcbData = sizeof(value);
result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
reinterpret_cast<LPBYTE>(value), &dwcbData);
if (result != ERROR_SUCCESS)
{
RegCloseKey(key);
continue;
}
aDriverVersion2 = o3tl::toU(value);
dwcbData = sizeof(value);
result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
reinterpret_cast<LPBYTE>(value), &dwcbData);
if (result != ERROR_SUCCESS)
{
RegCloseKey(key);
continue;
}
aDriverDate2 = o3tl::toU(value);
dwcbData = sizeof(value);
result = RegQueryValueExW(key, L"Device Description", nullptr,
nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
if (result != ERROR_SUCCESS)
{
dwcbData = sizeof(value);
result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr,
reinterpret_cast<LPBYTE>(value), &dwcbData);
}
RegCloseKey(key);
if (result == ERROR_SUCCESS)
{
mbHasDualGPU = true;
maDeviceString2 = o3tl::toU(value);
maDeviceID2 = aDeviceID2;
maDeviceKey2 = driverKey2;
maDriverVersion2 = aDriverVersion2;
maDriverDate2 = aDriverDate2;
appendIntegerWithPadding(maAdapterVendorID2, adapterVendorID2, 4);
appendIntegerWithPadding(maAdapterDeviceID2, adapterDeviceID2, 4);
appendIntegerWithPadding(maAdapterSubsysID2, ParseIDFromDeviceID(maDeviceID2, "&SUBSYS_", 8), 8);
break;
}
}
}
}
SetupDiDestroyDeviceInfoList(devinfo);
}
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */