Version 7.1.7.1, tag libreoffice-7.1.7.1
[LibreOffice.git] / vcl / opengl / win / WinDeviceInfo.cxx
blobc936aee11fa63d395c0d8a706907731de9ae4a18
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <opengl/win/WinDeviceInfo.hxx>
12 #include <driverblocklist.hxx>
13 #include <config_folders.h>
15 #if !defined WIN32_LEAN_AND_MEAN
16 # define WIN32_LEAN_AND_MEAN
17 #endif
18 #include <windows.h>
19 #include <objbase.h>
20 #include <setupapi.h>
21 #include <algorithm>
22 #include <cstdint>
23 #include <memory>
25 #include <osl/file.hxx>
26 #include <rtl/bootstrap.hxx>
27 #include <rtl/ustrbuf.hxx>
28 #include <sal/log.hxx>
29 #include <tools/stream.hxx>
30 #include <o3tl/char16_t2wchar_t.hxx>
32 #include <desktop/crashreport.hxx>
34 namespace {
36 bool GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, OUString& destString, int type)
38 HKEY key;
39 DWORD dwcbData;
40 DWORD dValue;
41 DWORD resultType;
42 LONG result;
43 bool retval = true;
45 result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key);
46 if (result != ERROR_SUCCESS)
48 return false;
51 switch (type)
53 case REG_DWORD:
55 // We only use this for vram size
56 dwcbData = sizeof(dValue);
57 result = RegQueryValueExW(key, keyName, nullptr, &resultType,
58 reinterpret_cast<LPBYTE>(&dValue), &dwcbData);
59 if (result == ERROR_SUCCESS && resultType == REG_DWORD)
61 dValue = dValue / 1024 / 1024;
62 destString += OUString::number(int32_t(dValue));
64 else
66 retval = false;
68 break;
70 case REG_MULTI_SZ:
72 // A chain of null-separated strings; we convert the nulls to spaces
73 WCHAR wCharValue[1024];
74 dwcbData = sizeof(wCharValue);
76 result = RegQueryValueExW(key, keyName, nullptr, &resultType,
77 reinterpret_cast<LPBYTE>(wCharValue), &dwcbData);
78 if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ)
80 // This bit here could probably be cleaner.
81 bool isValid = false;
83 DWORD strLen = dwcbData/sizeof(wCharValue[0]);
84 for (DWORD i = 0; i < strLen; i++)
86 if (wCharValue[i] == '\0')
88 if (i < strLen - 1 && wCharValue[i + 1] == '\0')
90 isValid = true;
91 break;
93 else
95 wCharValue[i] = ' ';
100 // ensure wCharValue is null terminated
101 wCharValue[strLen-1] = '\0';
103 if (isValid)
104 destString = OUString(o3tl::toU(wCharValue));
107 else
109 retval = false;
112 break;
115 RegCloseKey(key);
117 return retval;
120 // The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD
121 // this function is used to extract the id's out of it
122 uint32_t ParseIDFromDeviceID(const OUString &key, const char *prefix, int length)
124 OUString id = key.toAsciiUpperCase();
125 OUString aPrefix = OUString::fromUtf8(prefix);
126 int32_t start = id.indexOf(aPrefix);
127 if (start != -1)
129 id = id.copy(start + aPrefix.getLength(), length);
131 return id.toUInt32(16);
134 /* Other interesting places for info:
135 * IDXGIAdapter::GetDesc()
136 * IDirectDraw7::GetAvailableVidMem()
137 * e->GetAvailableTextureMem()
138 * */
140 template<typename T> void appendIntegerWithPadding(OUString& rString, T value, sal_uInt32 nChars)
142 rString += "0x";
143 OUString aValue = OUString::number(value, 16);
144 sal_Int32 nLength = aValue.getLength();
145 sal_uInt32 nPadLength = nChars - nLength;
146 assert(nPadLength >= 0);
147 OUStringBuffer aBuffer;
148 for (sal_uInt32 i = 0; i < nPadLength; ++i)
150 aBuffer.append("0");
152 rString += aBuffer.makeStringAndClear() + aValue;
155 #define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\"
158 WinOpenGLDeviceInfo::WinOpenGLDeviceInfo():
159 mbHasDualGPU(false),
160 mbRDP(false)
162 GetData();
165 WinOpenGLDeviceInfo::~WinOpenGLDeviceInfo()
169 static OUString getDenylistFile()
171 OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
172 rtl::Bootstrap::expandMacros(url);
174 return url + "/opengl/opengl_denylist_windows.xml";
177 bool WinOpenGLDeviceInfo::FindBlocklistedDeviceInList()
179 return DriverBlocklist::IsDeviceBlocked( getDenylistFile(), DriverBlocklist::VersionType::OpenGL,
180 maDriverVersion, maAdapterVendorID, maAdapterDeviceID);
183 namespace {
185 OUString getCacheFolder()
187 OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
188 rtl::Bootstrap::expandMacros(url);
190 osl::Directory::create(url);
192 return url;
195 void writeToLog(SvStream& rStrm, const char* pKey, const OUString & rVal)
197 rStrm.WriteCharPtr(pKey);
198 rStrm.WriteCharPtr(": ");
199 rStrm.WriteOString(OUStringToOString(rVal, RTL_TEXTENCODING_UTF8));
200 rStrm.WriteChar('\n');
205 bool WinOpenGLDeviceInfo::isDeviceBlocked()
207 CrashReporter::addKeyValue("OpenGLVendor", maAdapterVendorID, CrashReporter::AddItem);
208 CrashReporter::addKeyValue("OpenGLDevice", maAdapterDeviceID, CrashReporter::AddItem);
209 CrashReporter::addKeyValue("OpenGLDriver", maDriverVersion, CrashReporter::Write);
211 SAL_INFO("vcl.opengl", maDriverVersion);
212 SAL_INFO("vcl.opengl", maDriverDate);
213 SAL_INFO("vcl.opengl", maDeviceID);
214 SAL_INFO("vcl.opengl", maAdapterVendorID);
215 SAL_INFO("vcl.opengl", maAdapterDeviceID);
216 SAL_INFO("vcl.opengl", maAdapterSubsysID);
217 SAL_INFO("vcl.opengl", maDeviceKey);
218 SAL_INFO("vcl.opengl", maDeviceString);
220 OUString aCacheFolder = getCacheFolder();
222 OUString aCacheFile(aCacheFolder + "/opengl_device.log");
223 SvFileStream aOpenGLLogFile(aCacheFile, StreamMode::WRITE|StreamMode::TRUNC);
225 writeToLog(aOpenGLLogFile, "DriverVersion", maDriverVersion);
226 writeToLog(aOpenGLLogFile, "DriverDate", maDriverDate);
227 writeToLog(aOpenGLLogFile, "DeviceID", maDeviceID);
228 writeToLog(aOpenGLLogFile, "AdapterVendorID", maAdapterVendorID);
229 writeToLog(aOpenGLLogFile, "AdapterDeviceID", maAdapterDeviceID);
230 writeToLog(aOpenGLLogFile, "AdapterSubsysID", maAdapterSubsysID);
231 writeToLog(aOpenGLLogFile, "DeviceKey", maDeviceKey);
232 writeToLog(aOpenGLLogFile, "DeviceString", maDeviceString);
234 // Check if the device is blocked from the downloaded blocklist. If not, check
235 // the static list after that. This order is used so that we can later escape
236 // out of static blocks (i.e. if we were wrong or something was patched, we
237 // can back out our static block without doing a release).
238 if (mbRDP)
240 SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions");
241 return true;
244 return FindBlocklistedDeviceInList();
247 void WinOpenGLDeviceInfo::GetData()
249 DISPLAY_DEVICEW displayDevice;
250 displayDevice.cb = sizeof(displayDevice);
252 int deviceIndex = 0;
254 while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0))
256 if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
258 break;
260 deviceIndex++;
263 // make sure the string is null terminated
264 // (using the term "null" here to mean a zero UTF-16 unit)
265 if (wcsnlen(displayDevice.DeviceKey, SAL_N_ELEMENTS(displayDevice.DeviceKey))
266 == SAL_N_ELEMENTS(displayDevice.DeviceKey))
268 // we did not find a null
269 SAL_WARN("vcl.opengl", "string not null terminated");
270 return;
273 /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */
274 /* check that DeviceKey begins with DEVICE_KEY_PREFIX */
275 /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insensitively */
276 if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1) != 0)
278 SAL_WARN("vcl.opengl", "incorrect DeviceKey");
279 return;
282 // chop off DEVICE_KEY_PREFIX
283 maDeviceKey = o3tl::toU(displayDevice.DeviceKey) + SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1;
285 maDeviceID = o3tl::toU(displayDevice.DeviceID);
286 maDeviceString = o3tl::toU(displayDevice.DeviceString);
288 if (maDeviceID.isEmpty() &&
289 (maDeviceString == "RDPDD Chained DD" ||
290 (maDeviceString == "RDPUDD Chained DD")))
292 // we need to block RDP as it does not provide OpenGL 2.1+
293 mbRDP = true;
294 SAL_WARN("vcl.opengl", "RDP => blocked");
295 return;
298 /* create a device information set composed of the current display device */
299 HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, o3tl::toW(maDeviceID.getStr()), nullptr,
300 DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
302 if (devinfo != INVALID_HANDLE_VALUE)
304 HKEY key;
305 LONG result;
306 WCHAR value[255];
307 DWORD dwcbData;
308 SP_DEVINFO_DATA devinfoData;
309 DWORD memberIndex = 0;
311 devinfoData.cbSize = sizeof(devinfoData);
312 /* enumerate device information elements in the device information set */
313 while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
315 /* get a string that identifies the device's driver key */
316 if (SetupDiGetDeviceRegistryPropertyW(devinfo,
317 &devinfoData,
318 SPDRP_DRIVER,
319 nullptr,
320 reinterpret_cast<PBYTE>(value),
321 sizeof(value),
322 nullptr))
324 OUString driverKey(OUStringLiteral(u"System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
325 result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey.getStr()), 0, KEY_QUERY_VALUE, &key);
326 if (result == ERROR_SUCCESS)
328 /* we've found the driver we're looking for */
329 dwcbData = sizeof(value);
330 result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
331 reinterpret_cast<LPBYTE>(value), &dwcbData);
332 if (result == ERROR_SUCCESS)
334 maDriverVersion = OUString(o3tl::toU(value));
336 else
338 // If the entry wasn't found, assume the worst (0.0.0.0).
339 maDriverVersion = OUString("0.0.0.0");
341 dwcbData = sizeof(value);
342 result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
343 reinterpret_cast<LPBYTE>(value), &dwcbData);
344 if (result == ERROR_SUCCESS)
346 maDriverDate = o3tl::toU(value);
348 else
350 // Again, assume the worst
351 maDriverDate = OUString("01-01-1970");
353 RegCloseKey(key);
354 break;
359 SetupDiDestroyDeviceInfoList(devinfo);
361 else
363 SAL_WARN("vcl.opengl", "invalid handle value");
366 appendIntegerWithPadding(maAdapterVendorID, ParseIDFromDeviceID(maDeviceID, "VEN_", 4), 4);
367 appendIntegerWithPadding(maAdapterDeviceID, ParseIDFromDeviceID(maDeviceID, "&DEV_", 4), 4);
368 appendIntegerWithPadding(maAdapterSubsysID, ParseIDFromDeviceID(maDeviceID, "&SUBSYS_", 8), 8);
370 // We now check for second display adapter.
372 // Device interface class for display adapters.
373 CLSID GUID_DISPLAY_DEVICE_ARRIVAL;
374 HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}",
375 &GUID_DISPLAY_DEVICE_ARRIVAL);
376 if (hresult == NOERROR)
378 devinfo = SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL,
379 nullptr, nullptr,
380 DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
382 if (devinfo != INVALID_HANDLE_VALUE)
384 HKEY key;
385 LONG result;
386 WCHAR value[255];
387 DWORD dwcbData;
388 SP_DEVINFO_DATA devinfoData;
389 DWORD memberIndex = 0;
390 devinfoData.cbSize = sizeof(devinfoData);
392 OUString aAdapterDriver2;
393 OUString aDeviceID2;
394 OUString aDriverVersion2;
395 OUString aDriverDate2;
396 uint32_t adapterVendorID2;
397 uint32_t adapterDeviceID2;
399 /* enumerate device information elements in the device information set */
400 while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
402 /* get a string that identifies the device's driver key */
403 if (SetupDiGetDeviceRegistryPropertyW(devinfo,
404 &devinfoData,
405 SPDRP_DRIVER,
406 nullptr,
407 reinterpret_cast<PBYTE>(value),
408 sizeof(value),
409 nullptr))
411 OUString driverKey2(OUStringLiteral(u"System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
412 result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey2.getStr()), 0, KEY_QUERY_VALUE, &key);
413 if (result == ERROR_SUCCESS)
415 dwcbData = sizeof(value);
416 result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr,
417 nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
418 if (result != ERROR_SUCCESS)
420 continue;
422 aDeviceID2 = o3tl::toU(value);
423 OUString aAdapterVendorID2String;
424 OUString aAdapterDeviceID2String;
425 adapterVendorID2 = ParseIDFromDeviceID(aDeviceID2, "VEN_", 4);
426 appendIntegerWithPadding(aAdapterVendorID2String, adapterVendorID2, 4);
427 adapterDeviceID2 = ParseIDFromDeviceID(aDeviceID2, "&DEV_", 4);
428 appendIntegerWithPadding(aAdapterDeviceID2String, adapterDeviceID2, 4);
429 if (maAdapterVendorID == aAdapterVendorID2String &&
430 maAdapterDeviceID == aAdapterDeviceID2String)
432 RegCloseKey(key);
433 continue;
436 // If this device is missing driver information, it is unlikely to
437 // be a real display adapter.
438 if (!GetKeyValue(o3tl::toW(driverKey2.getStr()), L"InstalledDisplayDrivers",
439 aAdapterDriver2, REG_MULTI_SZ))
441 RegCloseKey(key);
442 continue;
444 dwcbData = sizeof(value);
445 result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
446 reinterpret_cast<LPBYTE>(value), &dwcbData);
447 if (result != ERROR_SUCCESS)
449 RegCloseKey(key);
450 continue;
452 aDriverVersion2 = o3tl::toU(value);
453 dwcbData = sizeof(value);
454 result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
455 reinterpret_cast<LPBYTE>(value), &dwcbData);
456 if (result != ERROR_SUCCESS)
458 RegCloseKey(key);
459 continue;
461 aDriverDate2 = o3tl::toU(value);
462 dwcbData = sizeof(value);
463 result = RegQueryValueExW(key, L"Device Description", nullptr,
464 nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
465 if (result != ERROR_SUCCESS)
467 dwcbData = sizeof(value);
468 result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr,
469 reinterpret_cast<LPBYTE>(value), &dwcbData);
471 RegCloseKey(key);
472 if (result == ERROR_SUCCESS)
474 mbHasDualGPU = true;
475 maDeviceString2 = o3tl::toU(value);
476 maDeviceID2 = aDeviceID2;
477 maDeviceKey2 = driverKey2;
478 maDriverVersion2 = aDriverVersion2;
479 maDriverDate2 = aDriverDate2;
480 appendIntegerWithPadding(maAdapterVendorID2, adapterVendorID2, 4);
481 appendIntegerWithPadding(maAdapterDeviceID2, adapterDeviceID2, 4);
482 appendIntegerWithPadding(maAdapterSubsysID2, ParseIDFromDeviceID(maDeviceID2, "&SUBSYS_", 8), 8);
483 break;
489 SetupDiDestroyDeviceInfoList(devinfo);
494 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */