LibreOffice Module vcl (master) 1
WinDeviceInfo.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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 */
9
11
12#include <driverblocklist.hxx>
13#include <config_folders.h>
14
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>
24#include <string_view>
25
26#include <osl/file.hxx>
27#include <rtl/bootstrap.hxx>
28#include <rtl/ustrbuf.hxx>
29#include <sal/log.hxx>
30#include <tools/stream.hxx>
32
34
35namespace {
36
37bool GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, OUString& destString, int type)
38{
39 HKEY key;
40 DWORD dwcbData;
41 DWORD dValue;
42 DWORD resultType;
44 bool retval = true;
45
46 result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key);
47 if (result != ERROR_SUCCESS)
48 {
49 return false;
50 }
51
52 switch (type)
53 {
54 case REG_DWORD:
55 {
56 // We only use this for vram size
57 dwcbData = sizeof(dValue);
58 result = RegQueryValueExW(key, keyName, nullptr, &resultType,
59 reinterpret_cast<LPBYTE>(&dValue), &dwcbData);
60 if (result == ERROR_SUCCESS && resultType == REG_DWORD)
61 {
62 dValue = dValue / 1024 / 1024;
63 destString += OUString::number(int32_t(dValue));
64 }
65 else
66 {
67 retval = false;
68 }
69 break;
70 }
71 case REG_MULTI_SZ:
72 {
73 // A chain of null-separated strings; we convert the nulls to spaces
74 WCHAR wCharValue[1024];
75 dwcbData = sizeof(wCharValue);
76
77 result = RegQueryValueExW(key, keyName, nullptr, &resultType,
78 reinterpret_cast<LPBYTE>(wCharValue), &dwcbData);
79 if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ)
80 {
81 // This bit here could probably be cleaner.
82 bool isValid = false;
83
84 DWORD strLen = dwcbData/sizeof(wCharValue[0]);
85 for (DWORD i = 0; i < strLen; i++)
86 {
87 if (wCharValue[i] == '\0')
88 {
89 if (i < strLen - 1 && wCharValue[i + 1] == '\0')
90 {
91 isValid = true;
92 break;
93 }
94 else
95 {
96 wCharValue[i] = ' ';
97 }
98 }
99 }
100
101 // ensure wCharValue is null terminated
102 wCharValue[strLen-1] = '\0';
103
104 if (isValid)
105 destString = OUString(o3tl::toU(wCharValue));
106
107 }
108 else
109 {
110 retval = false;
111 }
112
113 break;
114 }
115 }
116 RegCloseKey(key);
117
118 return retval;
119}
120
121// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD
122// this function is used to extract the id's out of it
123uint32_t ParseIDFromDeviceID(const OUString &key, const char *prefix, int length)
124{
125 OUString id = key.toAsciiUpperCase();
126 OUString aPrefix = OUString::fromUtf8(prefix);
127 int32_t start = id.indexOf(aPrefix);
128 if (start != -1)
129 {
130 id = id.copy(start + aPrefix.getLength(), length);
131 }
132 return id.toUInt32(16);
133}
134
135/* Other interesting places for info:
136 * IDXGIAdapter::GetDesc()
137 * IDirectDraw7::GetAvailableVidMem()
138 * e->GetAvailableTextureMem()
139 * */
140
141template<typename T> void appendIntegerWithPadding(OUString& rString, T value, sal_uInt32 nChars)
142{
143 rString += "0x";
144 OUString aValue = OUString::number(value, 16);
145 sal_Int32 nLength = aValue.getLength();
146 sal_uInt32 nPadLength = nChars - nLength;
147 assert(nPadLength >= 0);
148 OUStringBuffer aBuffer;
149 for (sal_uInt32 i = 0; i < nPadLength; ++i)
150 {
151 aBuffer.append("0");
152 }
153 rString += aBuffer.makeStringAndClear() + aValue;
154}
155
156#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\"
157}
158
160 mbHasDualGPU(false),
161 mbRDP(false)
162{
163 GetData();
164}
165
166static OUString getDenylistFile()
167{
168 OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
169 rtl::Bootstrap::expandMacros(url);
170
171 return url + "/opengl/opengl_denylist_windows.xml";
172}
173
175{
178}
179
180namespace {
181
182OUString getCacheFolder()
183{
184 OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
185 rtl::Bootstrap::expandMacros(url);
186
187 osl::Directory::create(url);
188
189 return url;
190}
191
192void writeToLog(SvStream& rStrm, const char* pKey, std::u16string_view rVal)
193{
194 rStrm.WriteOString(pKey);
195 rStrm.WriteOString(": ");
196 rStrm.WriteOString(OUStringToOString(rVal, RTL_TEXTENCODING_UTF8));
197 rStrm.WriteChar('\n');
198}
199
200}
201
203{
207
208 SAL_INFO("vcl.opengl", maDriverVersion);
209 SAL_INFO("vcl.opengl", maDriverDate);
210 SAL_INFO("vcl.opengl", maDeviceID);
211 SAL_INFO("vcl.opengl", maAdapterVendorID);
212 SAL_INFO("vcl.opengl", maAdapterDeviceID);
213 SAL_INFO("vcl.opengl", maAdapterSubsysID);
214 SAL_INFO("vcl.opengl", maDeviceKey);
215 SAL_INFO("vcl.opengl", maDeviceString);
216
217 OUString aCacheFolder = getCacheFolder();
218
219 OUString aCacheFile(aCacheFolder + "/opengl_device.log");
220 SvFileStream aOpenGLLogFile(aCacheFile, StreamMode::WRITE|StreamMode::TRUNC);
221
222 writeToLog(aOpenGLLogFile, "DriverVersion", maDriverVersion);
223 writeToLog(aOpenGLLogFile, "DriverDate", maDriverDate);
224 writeToLog(aOpenGLLogFile, "DeviceID", maDeviceID);
225 writeToLog(aOpenGLLogFile, "AdapterVendorID", maAdapterVendorID);
226 writeToLog(aOpenGLLogFile, "AdapterDeviceID", maAdapterDeviceID);
227 writeToLog(aOpenGLLogFile, "AdapterSubsysID", maAdapterSubsysID);
228 writeToLog(aOpenGLLogFile, "DeviceKey", maDeviceKey);
229 writeToLog(aOpenGLLogFile, "DeviceString", maDeviceString);
230
231 // Check if the device is blocked from the downloaded blocklist. If not, check
232 // the static list after that. This order is used so that we can later escape
233 // out of static blocks (i.e. if we were wrong or something was patched, we
234 // can back out our static block without doing a release).
235 if (mbRDP)
236 {
237 SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions");
238 return true;
239 }
240
242}
243
245{
246 DISPLAY_DEVICEW displayDevice;
247 displayDevice.cb = sizeof(displayDevice);
248
249 int deviceIndex = 0;
250
251 while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0))
252 {
253 if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
254 {
255 break;
256 }
257 deviceIndex++;
258 }
259
260 // make sure the string is null terminated
261 // (using the term "null" here to mean a zero UTF-16 unit)
262 if (wcsnlen(displayDevice.DeviceKey, SAL_N_ELEMENTS(displayDevice.DeviceKey))
263 == SAL_N_ELEMENTS(displayDevice.DeviceKey))
264 {
265 // we did not find a null
266 SAL_WARN("vcl.opengl", "string not null terminated");
267 return;
268 }
269
270 /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */
271 /* check that DeviceKey begins with DEVICE_KEY_PREFIX */
272 /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insensitively */
273 if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1) != 0)
274 {
275 SAL_WARN("vcl.opengl", "incorrect DeviceKey");
276 return;
277 }
278
279 // chop off DEVICE_KEY_PREFIX
280 maDeviceKey = o3tl::toU(displayDevice.DeviceKey) + SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1;
281
282 maDeviceID = o3tl::toU(displayDevice.DeviceID);
283 maDeviceString = o3tl::toU(displayDevice.DeviceString);
284
285 if (maDeviceID.isEmpty() &&
286 (maDeviceString == "RDPDD Chained DD" ||
287 (maDeviceString == "RDPUDD Chained DD")))
288 {
289 // we need to block RDP as it does not provide OpenGL 2.1+
290 mbRDP = true;
291 SAL_WARN("vcl.opengl", "RDP => blocked");
292 return;
293 }
294
295 /* create a device information set composed of the current display device */
296 HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, o3tl::toW(maDeviceID.getStr()), nullptr,
297 DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
298
299 if (devinfo != INVALID_HANDLE_VALUE)
300 {
301 HKEY key;
302 LONG result;
303 WCHAR value[255];
304 DWORD dwcbData;
305 SP_DEVINFO_DATA devinfoData;
306 DWORD memberIndex = 0;
307
308 devinfoData.cbSize = sizeof(devinfoData);
309 /* enumerate device information elements in the device information set */
310 while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
311 {
312 /* get a string that identifies the device's driver key */
313 if (SetupDiGetDeviceRegistryPropertyW(devinfo,
314 &devinfoData,
315 SPDRP_DRIVER,
316 nullptr,
317 reinterpret_cast<PBYTE>(value),
318 sizeof(value),
319 nullptr))
320 {
321 OUString driverKey(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
322 result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey.getStr()), 0, KEY_QUERY_VALUE, &key);
323 if (result == ERROR_SUCCESS)
324 {
325 /* we've found the driver we're looking for */
326 dwcbData = sizeof(value);
327 result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
328 reinterpret_cast<LPBYTE>(value), &dwcbData);
329 if (result == ERROR_SUCCESS)
330 {
331 maDriverVersion = OUString(o3tl::toU(value));
332 }
333 else
334 {
335 // If the entry wasn't found, assume the worst (0.0.0.0).
336 maDriverVersion = OUString("0.0.0.0");
337 }
338 dwcbData = sizeof(value);
339 result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
340 reinterpret_cast<LPBYTE>(value), &dwcbData);
341 if (result == ERROR_SUCCESS)
342 {
343 maDriverDate = o3tl::toU(value);
344 }
345 else
346 {
347 // Again, assume the worst
348 maDriverDate = OUString("01-01-1970");
349 }
350 RegCloseKey(key);
351 break;
352 }
353 }
354 }
355
356 SetupDiDestroyDeviceInfoList(devinfo);
357 }
358 else
359 {
360 SAL_WARN("vcl.opengl", "invalid handle value");
361 }
362
363 appendIntegerWithPadding(maAdapterVendorID, ParseIDFromDeviceID(maDeviceID, "VEN_", 4), 4);
364 appendIntegerWithPadding(maAdapterDeviceID, ParseIDFromDeviceID(maDeviceID, "&DEV_", 4), 4);
365 appendIntegerWithPadding(maAdapterSubsysID, ParseIDFromDeviceID(maDeviceID, "&SUBSYS_", 8), 8);
366
367 // We now check for second display adapter.
368
369 // Device interface class for display adapters.
370 CLSID GUID_DISPLAY_DEVICE_ARRIVAL;
371 HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}",
372 &GUID_DISPLAY_DEVICE_ARRIVAL);
373 if (hresult == NOERROR)
374 {
375 devinfo = SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL,
376 nullptr, nullptr,
377 DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
378
379 if (devinfo != INVALID_HANDLE_VALUE)
380 {
381 HKEY key;
382 LONG result;
383 WCHAR value[255];
384 DWORD dwcbData;
385 SP_DEVINFO_DATA devinfoData;
386 DWORD memberIndex = 0;
387 devinfoData.cbSize = sizeof(devinfoData);
388
389 OUString aAdapterDriver2;
390 OUString aDeviceID2;
391 OUString aDriverVersion2;
392 OUString aDriverDate2;
393 uint32_t adapterVendorID2;
394 uint32_t adapterDeviceID2;
395
396 /* enumerate device information elements in the device information set */
397 while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
398 {
399 /* get a string that identifies the device's driver key */
400 if (SetupDiGetDeviceRegistryPropertyW(devinfo,
401 &devinfoData,
402 SPDRP_DRIVER,
403 nullptr,
404 reinterpret_cast<PBYTE>(value),
405 sizeof(value),
406 nullptr))
407 {
408 OUString driverKey2(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
409 result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey2.getStr()), 0, KEY_QUERY_VALUE, &key);
410 if (result == ERROR_SUCCESS)
411 {
412 dwcbData = sizeof(value);
413 result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr,
414 nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
415 if (result != ERROR_SUCCESS)
416 {
417 continue;
418 }
419 aDeviceID2 = o3tl::toU(value);
420 OUString aAdapterVendorID2String;
421 OUString aAdapterDeviceID2String;
422 adapterVendorID2 = ParseIDFromDeviceID(aDeviceID2, "VEN_", 4);
423 appendIntegerWithPadding(aAdapterVendorID2String, adapterVendorID2, 4);
424 adapterDeviceID2 = ParseIDFromDeviceID(aDeviceID2, "&DEV_", 4);
425 appendIntegerWithPadding(aAdapterDeviceID2String, adapterDeviceID2, 4);
426 if (maAdapterVendorID == aAdapterVendorID2String &&
427 maAdapterDeviceID == aAdapterDeviceID2String)
428 {
429 RegCloseKey(key);
430 continue;
431 }
432
433 // If this device is missing driver information, it is unlikely to
434 // be a real display adapter.
435 if (!GetKeyValue(o3tl::toW(driverKey2.getStr()), L"InstalledDisplayDrivers",
436 aAdapterDriver2, REG_MULTI_SZ))
437 {
438 RegCloseKey(key);
439 continue;
440 }
441 dwcbData = sizeof(value);
442 result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
443 reinterpret_cast<LPBYTE>(value), &dwcbData);
444 if (result != ERROR_SUCCESS)
445 {
446 RegCloseKey(key);
447 continue;
448 }
449 aDriverVersion2 = o3tl::toU(value);
450 dwcbData = sizeof(value);
451 result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
452 reinterpret_cast<LPBYTE>(value), &dwcbData);
453 if (result != ERROR_SUCCESS)
454 {
455 RegCloseKey(key);
456 continue;
457 }
458 aDriverDate2 = o3tl::toU(value);
459 dwcbData = sizeof(value);
460 result = RegQueryValueExW(key, L"Device Description", nullptr,
461 nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
462 if (result != ERROR_SUCCESS)
463 {
464 dwcbData = sizeof(value);
465 result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr,
466 reinterpret_cast<LPBYTE>(value), &dwcbData);
467 }
468 RegCloseKey(key);
469 if (result == ERROR_SUCCESS)
470 {
471 mbHasDualGPU = true;
472 maDeviceString2 = o3tl::toU(value);
473 maDeviceID2 = aDeviceID2;
474 maDeviceKey2 = driverKey2;
475 maDriverVersion2 = aDriverVersion2;
476 maDriverDate2 = aDriverDate2;
477 appendIntegerWithPadding(maAdapterVendorID2, adapterVendorID2, 4);
478 appendIntegerWithPadding(maAdapterDeviceID2, adapterDeviceID2, 4);
479 appendIntegerWithPadding(maAdapterSubsysID2, ParseIDFromDeviceID(maDeviceID2, "&SUBSYS_", 8), 8);
480 break;
481 }
482 }
483 }
484 }
485
486 SetupDiDestroyDeviceInfoList(devinfo);
487 }
488 }
489}
490
491/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static OUString getDenylistFile()
#define DEVICE_KEY_PREFIX
static void addKeyValue(SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER tAddKeyHandling)
SvStream & WriteOString(std::string_view rStr)
SvStream & WriteChar(char nChar)
bool FindBlocklistedDeviceInList()
Any value
#define SAL_CONFIGFILE(name)
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
#define SAL_N_ELEMENTS(arr)
bool IsDeviceBlocked(const OUString &blocklistURL, VersionType versionType, std::u16string_view driverVersion, std::u16string_view vendorId, const OUString &deviceId)
int i
void SvStream & rStrm
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
LONG
Any result
ResultType type
std::unique_ptr< char[]> aBuffer
sal_Int32 nLength