1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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
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>
36 bool GetKeyValue(const WCHAR
* keyLocation
, const WCHAR
* keyName
, OUString
& destString
, int type
)
45 result
= RegOpenKeyExW(HKEY_LOCAL_MACHINE
, keyLocation
, 0, KEY_QUERY_VALUE
, &key
);
46 if (result
!= ERROR_SUCCESS
)
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
));
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.
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')
100 // ensure wCharValue is null terminated
101 wCharValue
[strLen
-1] = '\0';
104 destString
= OUString(o3tl::toU(wCharValue
));
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
);
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()
140 template<typename T
> void appendIntegerWithPadding(OUString
& rString
, T value
, sal_uInt32 nChars
)
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
)
152 rString
+= aBuffer
.makeStringAndClear() + aValue
;
155 #define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\"
158 WinOpenGLDeviceInfo::WinOpenGLDeviceInfo():
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
);
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
);
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).
240 SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions");
244 return FindBlocklistedDeviceInList();
247 void WinOpenGLDeviceInfo::GetData()
249 DISPLAY_DEVICEW displayDevice
;
250 displayDevice
.cb
= sizeof(displayDevice
);
254 while (EnumDisplayDevicesW(nullptr, deviceIndex
, &displayDevice
, 0))
256 if (displayDevice
.StateFlags
& DISPLAY_DEVICE_PRIMARY_DEVICE
)
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");
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");
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+
294 SAL_WARN("vcl.opengl", "RDP => blocked");
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
)
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
,
320 reinterpret_cast<PBYTE
>(value
),
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
));
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
);
350 // Again, assume the worst
351 maDriverDate
= OUString("01-01-1970");
359 SetupDiDestroyDeviceInfoList(devinfo
);
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
,
380 DIGCF_PRESENT
| DIGCF_INTERFACEDEVICE
);
382 if (devinfo
!= INVALID_HANDLE_VALUE
)
388 SP_DEVINFO_DATA devinfoData
;
389 DWORD memberIndex
= 0;
390 devinfoData
.cbSize
= sizeof(devinfoData
);
392 OUString aAdapterDriver2
;
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
,
407 reinterpret_cast<PBYTE
>(value
),
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
)
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
)
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
))
444 dwcbData
= sizeof(value
);
445 result
= RegQueryValueExW(key
, L
"DriverVersion", nullptr, nullptr,
446 reinterpret_cast<LPBYTE
>(value
), &dwcbData
);
447 if (result
!= ERROR_SUCCESS
)
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
)
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
);
472 if (result
== ERROR_SUCCESS
)
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);
489 SetupDiDestroyDeviceInfoList(devinfo
);
494 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */