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 
33 #include <desktop/crashreport.hxx>
34 
35 namespace {
36 
37 bool GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, OUString& destString, int type)
38 {
39  HKEY key;
40  DWORD dwcbData;
41  DWORD dValue;
42  DWORD resultType;
43  LONG result;
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
123 uint32_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 
141 template<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 
167 {
168 }
169 
170 static OUString getDenylistFile()
171 {
172  OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
173  rtl::Bootstrap::expandMacros(url);
174 
175  return url + "/opengl/opengl_denylist_windows.xml";
176 }
177 
179 {
182 }
183 
184 namespace {
185 
186 OUString getCacheFolder()
187 {
188  OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
189  rtl::Bootstrap::expandMacros(url);
190 
191  osl::Directory::create(url);
192 
193  return url;
194 }
195 
196 void writeToLog(SvStream& rStrm, const char* pKey, std::u16string_view rVal)
197 {
198  rStrm.WriteCharPtr(pKey);
199  rStrm.WriteCharPtr(": ");
200  rStrm.WriteOString(OUStringToOString(rVal, RTL_TEXTENCODING_UTF8));
201  rStrm.WriteChar('\n');
202 }
203 
204 }
205 
207 {
211 
212  SAL_INFO("vcl.opengl", maDriverVersion);
213  SAL_INFO("vcl.opengl", maDriverDate);
214  SAL_INFO("vcl.opengl", maDeviceID);
215  SAL_INFO("vcl.opengl", maAdapterVendorID);
216  SAL_INFO("vcl.opengl", maAdapterDeviceID);
217  SAL_INFO("vcl.opengl", maAdapterSubsysID);
218  SAL_INFO("vcl.opengl", maDeviceKey);
219  SAL_INFO("vcl.opengl", maDeviceString);
220 
221  OUString aCacheFolder = getCacheFolder();
222 
223  OUString aCacheFile(aCacheFolder + "/opengl_device.log");
224  SvFileStream aOpenGLLogFile(aCacheFile, StreamMode::WRITE|StreamMode::TRUNC);
225 
226  writeToLog(aOpenGLLogFile, "DriverVersion", maDriverVersion);
227  writeToLog(aOpenGLLogFile, "DriverDate", maDriverDate);
228  writeToLog(aOpenGLLogFile, "DeviceID", maDeviceID);
229  writeToLog(aOpenGLLogFile, "AdapterVendorID", maAdapterVendorID);
230  writeToLog(aOpenGLLogFile, "AdapterDeviceID", maAdapterDeviceID);
231  writeToLog(aOpenGLLogFile, "AdapterSubsysID", maAdapterSubsysID);
232  writeToLog(aOpenGLLogFile, "DeviceKey", maDeviceKey);
233  writeToLog(aOpenGLLogFile, "DeviceString", maDeviceString);
234 
235  // Check if the device is blocked from the downloaded blocklist. If not, check
236  // the static list after that. This order is used so that we can later escape
237  // out of static blocks (i.e. if we were wrong or something was patched, we
238  // can back out our static block without doing a release).
239  if (mbRDP)
240  {
241  SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions");
242  return true;
243  }
244 
246 }
247 
249 {
250  DISPLAY_DEVICEW displayDevice;
251  displayDevice.cb = sizeof(displayDevice);
252 
253  int deviceIndex = 0;
254 
255  while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0))
256  {
257  if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
258  {
259  break;
260  }
261  deviceIndex++;
262  }
263 
264  // make sure the string is null terminated
265  // (using the term "null" here to mean a zero UTF-16 unit)
266  if (wcsnlen(displayDevice.DeviceKey, SAL_N_ELEMENTS(displayDevice.DeviceKey))
267  == SAL_N_ELEMENTS(displayDevice.DeviceKey))
268  {
269  // we did not find a null
270  SAL_WARN("vcl.opengl", "string not null terminated");
271  return;
272  }
273 
274  /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */
275  /* check that DeviceKey begins with DEVICE_KEY_PREFIX */
276  /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insensitively */
277  if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1) != 0)
278  {
279  SAL_WARN("vcl.opengl", "incorrect DeviceKey");
280  return;
281  }
282 
283  // chop off DEVICE_KEY_PREFIX
284  maDeviceKey = o3tl::toU(displayDevice.DeviceKey) + SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1;
285 
286  maDeviceID = o3tl::toU(displayDevice.DeviceID);
287  maDeviceString = o3tl::toU(displayDevice.DeviceString);
288 
289  if (maDeviceID.isEmpty() &&
290  (maDeviceString == "RDPDD Chained DD" ||
291  (maDeviceString == "RDPUDD Chained DD")))
292  {
293  // we need to block RDP as it does not provide OpenGL 2.1+
294  mbRDP = true;
295  SAL_WARN("vcl.opengl", "RDP => blocked");
296  return;
297  }
298 
299  /* create a device information set composed of the current display device */
300  HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, o3tl::toW(maDeviceID.getStr()), nullptr,
301  DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
302 
303  if (devinfo != INVALID_HANDLE_VALUE)
304  {
305  HKEY key;
306  LONG result;
307  WCHAR value[255];
308  DWORD dwcbData;
309  SP_DEVINFO_DATA devinfoData;
310  DWORD memberIndex = 0;
311 
312  devinfoData.cbSize = sizeof(devinfoData);
313  /* enumerate device information elements in the device information set */
314  while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
315  {
316  /* get a string that identifies the device's driver key */
317  if (SetupDiGetDeviceRegistryPropertyW(devinfo,
318  &devinfoData,
319  SPDRP_DRIVER,
320  nullptr,
321  reinterpret_cast<PBYTE>(value),
322  sizeof(value),
323  nullptr))
324  {
325  OUString driverKey(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
326  result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey.getStr()), 0, KEY_QUERY_VALUE, &key);
327  if (result == ERROR_SUCCESS)
328  {
329  /* we've found the driver we're looking for */
330  dwcbData = sizeof(value);
331  result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
332  reinterpret_cast<LPBYTE>(value), &dwcbData);
333  if (result == ERROR_SUCCESS)
334  {
335  maDriverVersion = OUString(o3tl::toU(value));
336  }
337  else
338  {
339  // If the entry wasn't found, assume the worst (0.0.0.0).
340  maDriverVersion = OUString("0.0.0.0");
341  }
342  dwcbData = sizeof(value);
343  result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
344  reinterpret_cast<LPBYTE>(value), &dwcbData);
345  if (result == ERROR_SUCCESS)
346  {
347  maDriverDate = o3tl::toU(value);
348  }
349  else
350  {
351  // Again, assume the worst
352  maDriverDate = OUString("01-01-1970");
353  }
354  RegCloseKey(key);
355  break;
356  }
357  }
358  }
359 
360  SetupDiDestroyDeviceInfoList(devinfo);
361  }
362  else
363  {
364  SAL_WARN("vcl.opengl", "invalid handle value");
365  }
366 
367  appendIntegerWithPadding(maAdapterVendorID, ParseIDFromDeviceID(maDeviceID, "VEN_", 4), 4);
368  appendIntegerWithPadding(maAdapterDeviceID, ParseIDFromDeviceID(maDeviceID, "&DEV_", 4), 4);
369  appendIntegerWithPadding(maAdapterSubsysID, ParseIDFromDeviceID(maDeviceID, "&SUBSYS_", 8), 8);
370 
371  // We now check for second display adapter.
372 
373  // Device interface class for display adapters.
374  CLSID GUID_DISPLAY_DEVICE_ARRIVAL;
375  HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}",
376  &GUID_DISPLAY_DEVICE_ARRIVAL);
377  if (hresult == NOERROR)
378  {
379  devinfo = SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL,
380  nullptr, nullptr,
381  DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
382 
383  if (devinfo != INVALID_HANDLE_VALUE)
384  {
385  HKEY key;
386  LONG result;
387  WCHAR value[255];
388  DWORD dwcbData;
389  SP_DEVINFO_DATA devinfoData;
390  DWORD memberIndex = 0;
391  devinfoData.cbSize = sizeof(devinfoData);
392 
393  OUString aAdapterDriver2;
394  OUString aDeviceID2;
395  OUString aDriverVersion2;
396  OUString aDriverDate2;
397  uint32_t adapterVendorID2;
398  uint32_t adapterDeviceID2;
399 
400  /* enumerate device information elements in the device information set */
401  while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
402  {
403  /* get a string that identifies the device's driver key */
404  if (SetupDiGetDeviceRegistryPropertyW(devinfo,
405  &devinfoData,
406  SPDRP_DRIVER,
407  nullptr,
408  reinterpret_cast<PBYTE>(value),
409  sizeof(value),
410  nullptr))
411  {
412  OUString driverKey2(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
413  result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey2.getStr()), 0, KEY_QUERY_VALUE, &key);
414  if (result == ERROR_SUCCESS)
415  {
416  dwcbData = sizeof(value);
417  result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr,
418  nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
419  if (result != ERROR_SUCCESS)
420  {
421  continue;
422  }
423  aDeviceID2 = o3tl::toU(value);
424  OUString aAdapterVendorID2String;
425  OUString aAdapterDeviceID2String;
426  adapterVendorID2 = ParseIDFromDeviceID(aDeviceID2, "VEN_", 4);
427  appendIntegerWithPadding(aAdapterVendorID2String, adapterVendorID2, 4);
428  adapterDeviceID2 = ParseIDFromDeviceID(aDeviceID2, "&DEV_", 4);
429  appendIntegerWithPadding(aAdapterDeviceID2String, adapterDeviceID2, 4);
430  if (maAdapterVendorID == aAdapterVendorID2String &&
431  maAdapterDeviceID == aAdapterDeviceID2String)
432  {
433  RegCloseKey(key);
434  continue;
435  }
436 
437  // If this device is missing driver information, it is unlikely to
438  // be a real display adapter.
439  if (!GetKeyValue(o3tl::toW(driverKey2.getStr()), L"InstalledDisplayDrivers",
440  aAdapterDriver2, REG_MULTI_SZ))
441  {
442  RegCloseKey(key);
443  continue;
444  }
445  dwcbData = sizeof(value);
446  result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
447  reinterpret_cast<LPBYTE>(value), &dwcbData);
448  if (result != ERROR_SUCCESS)
449  {
450  RegCloseKey(key);
451  continue;
452  }
453  aDriverVersion2 = o3tl::toU(value);
454  dwcbData = sizeof(value);
455  result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
456  reinterpret_cast<LPBYTE>(value), &dwcbData);
457  if (result != ERROR_SUCCESS)
458  {
459  RegCloseKey(key);
460  continue;
461  }
462  aDriverDate2 = o3tl::toU(value);
463  dwcbData = sizeof(value);
464  result = RegQueryValueExW(key, L"Device Description", nullptr,
465  nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
466  if (result != ERROR_SUCCESS)
467  {
468  dwcbData = sizeof(value);
469  result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr,
470  reinterpret_cast<LPBYTE>(value), &dwcbData);
471  }
472  RegCloseKey(key);
473  if (result == ERROR_SUCCESS)
474  {
475  mbHasDualGPU = true;
476  maDeviceString2 = o3tl::toU(value);
477  maDeviceID2 = aDeviceID2;
478  maDeviceKey2 = driverKey2;
479  maDriverVersion2 = aDriverVersion2;
480  maDriverDate2 = aDriverDate2;
481  appendIntegerWithPadding(maAdapterVendorID2, adapterVendorID2, 4);
482  appendIntegerWithPadding(maAdapterDeviceID2, adapterDeviceID2, 4);
483  appendIntegerWithPadding(maAdapterSubsysID2, ParseIDFromDeviceID(maDeviceID2, "&SUBSYS_", 8), 8);
484  break;
485  }
486  }
487  }
488  }
489 
490  SetupDiDestroyDeviceInfoList(devinfo);
491  }
492  }
493 }
494 
495 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
virtual ~WinOpenGLDeviceInfo() override
bool FindBlocklistedDeviceInList()
LONG
SvStream & WriteCharPtr(const char *pBuf)
static OUString getDenylistFile()
bool IsDeviceBlocked(const OUString &blocklistURL, VersionType versionType, std::u16string_view driverVersion, std::u16string_view vendorId, const OUString &deviceId)
virtual bool isDeviceBlocked() override
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
#define SAL_N_ELEMENTS(arr)
#define SAL_CONFIGFILE(name)
int i
std::unique_ptr< char[]> aBuffer
SvStream & WriteOString(std::string_view rStr)
#define DEVICE_KEY_PREFIX
#define SAL_INFO(area, stream)
Any value
SvStream & WriteChar(char nChar)
Any result
ResultType type
#define SAL_WARN(area, stream)
sal_Int32 nLength
static void addKeyValue(SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER tAddKeyHandling)