diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 8f41efa47d..d2fad5d305 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -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 diff --git a/src/plugins/platforms/direct2d/CMakeLists.txt b/src/plugins/platforms/direct2d/CMakeLists.txt index 20c19929be..e21b230cd7 100644 --- a/src/plugins/platforms/direct2d/CMakeLists.txt +++ b/src/plugins/platforms/direct2d/CMakeLists.txt @@ -68,6 +68,7 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin imm32 ole32 oleaut32 + setupapi shell32 shlwapi user32 diff --git a/src/plugins/platforms/windows/CMakeLists.txt b/src/plugins/platforms/windows/CMakeLists.txt index 333b7d67f1..ca1bbcb758 100644 --- a/src/plugins/platforms/windows/CMakeLists.txt +++ b/src/plugins/platforms/windows/CMakeLists.txt @@ -56,6 +56,7 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin imm32 ole32 oleaut32 + setupapi shell32 shlwapi user32 diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp index 5414fc2778..6b85a68f69 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -22,6 +24,11 @@ #include +#include +#include + +#include +#include #include 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 getPathInfo(const MONITORINFOEX &viewInfo) { // We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO. std::vector 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++) { - 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; - if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) { - if (wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) == 0) { - *pathInfo = pathInfos[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 = path.sourceInfo.adapterId; + deviceName.header.id = path.sourceInfo.id; + if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) { + 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; +struct RegistryHandleDeleter +{ + void operator()(HKEY handle) const noexcept + { + if (handle != nullptr && handle != INVALID_HANDLE_VALUE) + RegCloseKey(handle); + } +}; + +using RegistryHandlePtr = std::unique_ptr, RegistryHandleDeleter>; + +static void setMonitorDataFromSetupApi(QWindowsScreenData &data, + const std::vector &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 storage(new std::byte[requiredSize]); + auto *devicePath = reinterpret_cast(storage.get()); + devicePath->cbSize = sizeof(std::remove_pointer_t); + 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(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) diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h index b1c94d2204..a400fd7e76 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.h +++ b/src/plugins/platforms/windows/qwindowsscreen.h @@ -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 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 virtualSiblings() const override; QWindow *topLevelAt(const QPoint &point) const override;