Windows: implement retrieval for missing QScreen metadata

This commit implements the manufacturer, model, and serialNumber
properties of QScreen for the Windows operating system. These were not
available previously because the Display Devices API allows piecemeal
access to the EDID fields, and the GDI API only returns the i-th device
name, e.g. "\\.\DISPLAY1".

Accessing the EDID of a given screen is possible by, given a
WindowsScreenData instance storing the device path, pivoting from
SetupDiOpenDeviceInterfaceW and then extracting the corresponding blob
from the Registry through a key handle retrieved with
SetupDiOpenDevRegKey. This blob can be parsed just like in Linux with
the QEdidParser class. The resulting metadata is applied to
the WindowsScreenData instance.

Additionally, this commit implements support for clone groups by making
getPathInfo return a list of the matching DISPLAYCONFIG_PATH_INFO
instances, and then concatenating the monitorFriendlyDeviceName
and the EDID manufacturer, model and serialNumber properties.

This commit makes the Windows and Direct2D QPA plugins dependent on
setupapi, and extends the QEdidParser class availability condition to
include these platforms.

Pick-to: 6.5
Change-Id: I56886b035a3d15e6f90aad5d797aeda21f99ff74
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
This commit is contained in:
L. E. Segovia 2023-03-16 18:03:28 -03:00
parent fdd0f7f8af
commit 6136b92f54
5 changed files with 187 additions and 39 deletions

View File

@ -981,7 +981,7 @@ qt_internal_extend_target(Gui
"painting/qdrawhelper.cpp"
)
qt_internal_extend_target(Gui CONDITION (QT_FEATURE_eglfs OR QT_FEATURE_xcb)
qt_internal_extend_target(Gui CONDITION (QT_FEATURE_eglfs OR QT_FEATURE_xcb OR QT_FEATURE_direct2d OR WIN32)
SOURCES
util/qedidparser.cpp util/qedidparser_p.h
util/qedidvendortable_p.h

View File

@ -68,6 +68,7 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin
imm32
ole32
oleaut32
setupapi
shell32
shlwapi
user32

View File

@ -56,6 +56,7 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin
imm32
ole32
oleaut32
setupapi
shell32
shlwapi
user32

View File

@ -14,6 +14,8 @@
#include <QtGui/qpixmap.h>
#include <QtGui/qguiapplication.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/private/qsystemerror_p.h>
#include <QtGui/private/qedidparser_p.h>
#include <private/qhighdpiscaling_p.h>
#include <private/qwindowsfontdatabasebase_p.h>
#include <private/qpixmap_win_p.h>
@ -22,6 +24,11 @@
#include <QtCore/qdebug.h>
#include <memory>
#include <type_traits>
#include <cfgmgr32.h>
#include <setupapi.h>
#include <shellscalingapi.h>
QT_BEGIN_NAMESPACE
@ -42,7 +49,7 @@ static inline QDpi monitorDPI(HMONITOR hMonitor)
return {0, 0};
}
static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO *pathInfo)
static std::vector<DISPLAYCONFIG_PATH_INFO> getPathInfo(const MONITORINFOEX &viewInfo)
{
// We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO.
std::vector<DISPLAYCONFIG_PATH_INFO> pathInfos;
@ -58,7 +65,7 @@ static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO *
// look up the needed buffer sizes.
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements,
&numModeInfoArrayElements) != ERROR_SUCCESS) {
return false;
return {};
}
pathInfos.resize(numPathArrayElements);
modeInfos.resize(numModeInfoArrayElements);
@ -67,24 +74,25 @@ static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO *
} while (result == ERROR_INSUFFICIENT_BUFFER);
if (result != ERROR_SUCCESS)
return false;
return {};
// Find path matching monitor name
for (uint32_t p = 0; p < numPathArrayElements; p++) {
// Find paths matching monitor name
auto discardThese =
std::remove_if(pathInfos.begin(), pathInfos.end(), [&](const auto &path) -> bool {
DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName;
deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
deviceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
deviceName.header.adapterId = pathInfos[p].sourceInfo.adapterId;
deviceName.header.id = pathInfos[p].sourceInfo.id;
deviceName.header.adapterId = path.sourceInfo.adapterId;
deviceName.header.id = path.sourceInfo.id;
if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
if (wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) == 0) {
*pathInfo = pathInfos[p];
return wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) != 0;
}
return true;
}
}
}
});
return false;
pathInfos.erase(discardThese, pathInfos.end());
return pathInfos;
}
#if 0
@ -108,6 +116,143 @@ static float getMonitorSDRWhiteLevel(DISPLAYCONFIG_PATH_TARGET_INFO *targetInfo)
using WindowsScreenDataList = QList<QWindowsScreenData>;
struct RegistryHandleDeleter
{
void operator()(HKEY handle) const noexcept
{
if (handle != nullptr && handle != INVALID_HANDLE_VALUE)
RegCloseKey(handle);
}
};
using RegistryHandlePtr = std::unique_ptr<std::remove_pointer_t<HKEY>, RegistryHandleDeleter>;
static void setMonitorDataFromSetupApi(QWindowsScreenData &data,
const std::vector<DISPLAYCONFIG_PATH_INFO> &pathGroup)
{
if (pathGroup.empty()) {
return;
}
// The only property shared among monitors in a clone group is deviceName
{
DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
// The first element in the clone group is the main monitor.
deviceName.header.adapterId = pathGroup[0].targetInfo.adapterId;
deviceName.header.id = pathGroup[0].targetInfo.id;
if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
data.devicePath = QString::fromWCharArray(deviceName.monitorDevicePath);
} else {
qCWarning(lcQpaScreen)
<< u"Unable to get device information for %1:"_s.arg(pathGroup[0].targetInfo.id)
<< QSystemError::windowsString();
}
}
// The rest must be concatenated into the resulting property
QStringList names;
QStringList manufacturers;
QStringList models;
QStringList serialNumbers;
for (const auto &path : pathGroup) {
DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
deviceName.header.adapterId = path.targetInfo.adapterId;
deviceName.header.id = path.targetInfo.id;
if (DisplayConfigGetDeviceInfo(&deviceName.header) != ERROR_SUCCESS) {
qCWarning(lcQpaScreen)
<< u"Unable to get device information for %1:"_s.arg(path.targetInfo.id)
<< QSystemError::windowsString();
continue;
}
// https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-monitor
constexpr GUID GUID_DEVINTERFACE_MONITOR = {
0xe6f07b5f, 0xee97, 0x4a90, { 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 }
};
const HDEVINFO devInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_MONITOR, nullptr, nullptr,
DIGCF_DEVICEINTERFACE);
SP_DEVICE_INTERFACE_DATA deviceInterfaceData{};
deviceInterfaceData.cbSize = sizeof(deviceInterfaceData);
if (!SetupDiOpenDeviceInterfaceW(devInfo, deviceName.monitorDevicePath, DIODI_NO_ADD,
&deviceInterfaceData)) {
qCWarning(lcQpaScreen)
<< u"Unable to open monitor interface to %1:"_s.arg(data.deviceName)
<< QSystemError::windowsString();
continue;
}
DWORD requiredSize{ 0 };
if (SetupDiGetDeviceInterfaceDetailW(devInfo, &deviceInterfaceData, nullptr, 0,
&requiredSize, nullptr)
|| GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
continue;
}
const std::unique_ptr<std::byte[]> storage(new std::byte[requiredSize]);
auto *devicePath = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W *>(storage.get());
devicePath->cbSize = sizeof(std::remove_pointer_t<decltype(devicePath)>);
SP_DEVINFO_DATA deviceInfoData{};
deviceInfoData.cbSize = sizeof(deviceInfoData);
if (!SetupDiGetDeviceInterfaceDetailW(devInfo, &deviceInterfaceData, devicePath,
requiredSize, nullptr, &deviceInfoData)) {
qCDebug(lcQpaScreen) << u"Unable to get monitor metadata for %1:"_s.arg(data.deviceName)
<< QSystemError::windowsString();
continue;
}
const RegistryHandlePtr edidRegistryKey{ SetupDiOpenDevRegKey(
devInfo, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ) };
if (!edidRegistryKey || edidRegistryKey.get() == INVALID_HANDLE_VALUE)
continue;
DWORD edidDataSize{ 0 };
if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr, nullptr,
&edidDataSize)
!= ERROR_SUCCESS) {
continue;
}
QByteArray edidData;
edidData.resize(edidDataSize);
if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr,
reinterpret_cast<unsigned char *>(edidData.data()), &edidDataSize)
!= ERROR_SUCCESS) {
qCDebug(lcQpaScreen) << u"Unable to get EDID from the Registry for %1:"_s.arg(
data.deviceName)
<< QSystemError::windowsString();
continue;
}
QEdidParser edid;
if (!edid.parse(edidData)) {
qCDebug(lcQpaScreen) << "Invalid EDID blob for" << data.deviceName;
continue;
}
// We skip edid.identifier because it is unreliable, and a better option
// is already available through DisplayConfigGetDeviceInfo (see below).
names << QString::fromWCharArray(deviceName.monitorFriendlyDeviceName);
manufacturers << edid.manufacturer;
models << edid.model;
serialNumbers << edid.serialNumber;
}
data.name = names.join(u"|"_s);
data.manufacturer = manufacturers.join(u"|"_s);
data.model = models.join(u"|"_s);
data.serialNumber = serialNumbers.join(u"|"_s);
}
static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
{
MONITORINFOEX info;
@ -120,16 +265,9 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
data->geometry = QRect(QPoint(info.rcMonitor.left, info.rcMonitor.top), QPoint(info.rcMonitor.right - 1, info.rcMonitor.bottom - 1));
data->availableGeometry = QRect(QPoint(info.rcWork.left, info.rcWork.top), QPoint(info.rcWork.right - 1, info.rcWork.bottom - 1));
data->deviceName = QString::fromWCharArray(info.szDevice);
DISPLAYCONFIG_PATH_INFO pathInfo = {};
const bool hasPathInfo = getPathInfo(info, &pathInfo);
if (hasPathInfo) {
DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
deviceName.header.adapterId = pathInfo.targetInfo.adapterId;
deviceName.header.id = pathInfo.targetInfo.id;
if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS)
data->name = QString::fromWCharArray(deviceName.monitorFriendlyDeviceName);
const auto pathGroup = getPathInfo(info);
if (!pathGroup.empty()) {
setMonitorDataFromSetupApi(*data, pathGroup);
}
if (data->name.isEmpty())
data->name = data->deviceName;
@ -155,7 +293,9 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
// ### We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO,
// if we are going to use DISPLAYCONFIG lookups more.
if (hasPathInfo) {
if (!pathGroup.empty()) {
// The first element in the clone group is the main monitor.
const auto &pathInfo = pathGroup[0];
switch (pathInfo.targetInfo.rotation) {
case DISPLAYCONFIG_ROTATION_IDENTITY:
data->orientation = Qt::LandscapeOrientation;
@ -231,15 +371,14 @@ static QDebug operator<<(QDebug dbg, const QWindowsScreenData &d)
QDebugStateSaver saver(dbg);
dbg.nospace();
dbg.noquote();
dbg << "Screen \"" << d.name << "\" "
<< d.geometry.width() << 'x' << d.geometry.height() << '+' << d.geometry.x() << '+' << d.geometry.y()
<< " avail: "
<< d.availableGeometry.width() << 'x' << d.availableGeometry.height() << '+' << d.availableGeometry.x() << '+' << d.availableGeometry.y()
<< " physical: " << d.physicalSizeMM.width() << 'x' << d.physicalSizeMM.height()
<< " DPI: " << d.dpi.first << 'x' << d.dpi.second << " Depth: " << d.depth
<< " Format: " << d.format
<< " hMonitor: " << d.hMonitor
<< " device name: " << d.deviceName;
dbg << "Screen \"" << d.name << "\" " << d.geometry.width() << 'x' << d.geometry.height() << '+'
<< d.geometry.x() << '+' << d.geometry.y() << " avail: " << d.availableGeometry.width()
<< 'x' << d.availableGeometry.height() << '+' << d.availableGeometry.x() << '+'
<< d.availableGeometry.y() << " physical: " << d.physicalSizeMM.width() << 'x'
<< d.physicalSizeMM.height() << " DPI: " << d.dpi.first << 'x' << d.dpi.second
<< " Depth: " << d.depth << " Format: " << d.format << " hMonitor: " << d.hMonitor
<< " device name: " << d.deviceName << " manufacturer: " << d.manufacturer
<< " model: " << d.model << " serial number: " << d.serialNumber;
if (d.flags & QWindowsScreenData::PrimaryScreen)
dbg << " primary";
if (d.flags & QWindowsScreenData::VirtualDesktop)

View File

@ -31,10 +31,14 @@ struct QWindowsScreenData
QImage::Format format = QImage::Format_ARGB32_Premultiplied;
unsigned flags = VirtualDesktop;
QString name;
QString manufacturer;
QString model;
QString serialNumber;
Qt::ScreenOrientation orientation = Qt::LandscapeOrientation;
qreal refreshRateHz = 60;
HMONITOR hMonitor = nullptr;
QString deviceName = {};
QString deviceName;
QString devicePath;
std::optional<int> deviceIndex = std::nullopt;
};
@ -58,6 +62,9 @@ public:
qreal devicePixelRatio() const override { return 1.0; }
qreal refreshRate() const override { return m_data.refreshRateHz; }
QString name() const override;
QString manufacturer() const override { return m_data.manufacturer; }
QString model() const override { return m_data.model; }
QString serialNumber() const override { return m_data.serialNumber; }
Qt::ScreenOrientation orientation() const override { return m_data.orientation; }
QList<QPlatformScreen *> virtualSiblings() const override;
QWindow *topLevelAt(const QPoint &point) const override;