diff options
Diffstat (limited to 'src/plugins/platforms/windows/qwindowsscreen.cpp')
-rw-r--r-- | src/plugins/platforms/windows/qwindowsscreen.cpp | 537 |
1 files changed, 390 insertions, 147 deletions
diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp index c7a0c2e62e..a50f9fd4b0 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwindowsscreen.h" #include "qwindowscontext.h" @@ -50,13 +14,28 @@ #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> +#include <private/quniquehandle_p.h> + #include <QtGui/qscreen.h> #include <QtCore/qdebug.h> +#include <memory> +#include <type_traits> + +#include <cfgmgr32.h> +#include <setupapi.h> +#include <shellscalingapi.h> + QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + static inline QDpi deviceDPI(HDC hdc) { return QDpi(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY)); @@ -64,16 +43,222 @@ static inline QDpi deviceDPI(HDC hdc) static inline QDpi monitorDPI(HMONITOR hMonitor) { - if (QWindowsContext::shcoredll.isValid()) { - UINT dpiX; - UINT dpiY; - if (SUCCEEDED(QWindowsContext::shcoredll.getDpiForMonitor(hMonitor, 0, &dpiX, &dpiY))) - return QDpi(dpiX, dpiY); - } + UINT dpiX; + UINT dpiY; + if (SUCCEEDED(GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) + return QDpi(dpiX, dpiY); return {0, 0}; } -using WindowsScreenDataList = QVector<QWindowsScreenData>; +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; + std::vector<DISPLAYCONFIG_MODE_INFO> modeInfos; + + // Fetch paths + LONG result; + UINT32 numPathArrayElements; + UINT32 numModeInfoArrayElements; + do { + // QueryDisplayConfig documentation doesn't say the number of needed elements is updated + // when the call fails with ERROR_INSUFFICIENT_BUFFER, so we need a separate call to + // look up the needed buffer sizes. + if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements, + &numModeInfoArrayElements) != ERROR_SUCCESS) { + return {}; + } + pathInfos.resize(numPathArrayElements); + modeInfos.resize(numModeInfoArrayElements); + result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements, pathInfos.data(), + &numModeInfoArrayElements, modeInfos.data(), nullptr); + } while (result == ERROR_INSUFFICIENT_BUFFER); + + if (result != ERROR_SUCCESS) + return {}; + + // 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; + }); + + pathInfos.erase(discardThese, pathInfos.end()); + + return pathInfos; +} + +#if 0 +// Needed later for HDR support +static float getMonitorSDRWhiteLevel(DISPLAYCONFIG_PATH_TARGET_INFO *targetInfo) +{ + const float defaultSdrWhiteLevel = 200.0; + if (!targetInfo) + return defaultSdrWhiteLevel; + + DISPLAYCONFIG_SDR_WHITE_LEVEL whiteLevel = {}; + whiteLevel.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL; + whiteLevel.header.size = sizeof(DISPLAYCONFIG_SDR_WHITE_LEVEL); + whiteLevel.header.adapterId = targetInfo->adapterId; + whiteLevel.header.id = targetInfo->id; + if (DisplayConfigGetDeviceInfo(&whiteLevel.header) != ERROR_SUCCESS) + return defaultSdrWhiteLevel; + return whiteLevel.SDRWhiteLevel * 80.0 / 1000.0; +} +#endif + +using WindowsScreenDataList = QList<QWindowsScreenData>; + +namespace { + +struct DiRegKeyHandleTraits +{ + using Type = HKEY; + static Type invalidValue() + { + // The setupapi.h functions return INVALID_HANDLE_VALUE when failing to open a registry key + return reinterpret_cast<HKEY>(INVALID_HANDLE_VALUE); + } + static bool close(Type handle) { return RegCloseKey(handle) == ERROR_SUCCESS; } +}; + +using DiRegKeyHandle = QUniqueHandle<DiRegKeyHandleTraits>; + +} + +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 DiRegKeyHandle edidRegistryKey{ SetupDiOpenDevRegKey( + devInfo, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ) }; + + if (!edidRegistryKey.isValid()) + 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) { @@ -86,8 +271,14 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data) data->hMonitor = hMonitor; 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->name = QString::fromWCharArray(info.szDevice); - if (data->name == u"WinDisc") { + data->deviceName = QString::fromWCharArray(info.szDevice); + const auto pathGroup = getPathInfo(info); + if (!pathGroup.empty()) { + setMonitorDataFromSetupApi(*data, pathGroup); + } + if (data->name.isEmpty()) + data->name = data->deviceName; + if (data->deviceName == u"WinDisc") { data->flags |= QWindowsScreenData::LockScreen; } else { if (const HDC hdc = CreateDC(info.szDevice, nullptr, nullptr, nullptr)) { @@ -102,12 +293,41 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data) DeleteDC(hdc); } else { qWarning("%s: Unable to obtain handle for monitor '%s', defaulting to %g DPI.", - __FUNCTION__, qPrintable(QString::fromWCharArray(info.szDevice)), + __FUNCTION__, qPrintable(data->deviceName), data->dpi.first); } // CreateDC() failed } // not lock screen - data->orientation = data->geometry.height() > data->geometry.width() ? - Qt::PortraitOrientation : Qt::LandscapeOrientation; + + // ### We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO, + // if we are going to use DISPLAYCONFIG lookups more. + 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; + break; + case DISPLAYCONFIG_ROTATION_ROTATE90: + data->orientation = Qt::PortraitOrientation; + break; + case DISPLAYCONFIG_ROTATION_ROTATE180: + data->orientation = Qt::InvertedLandscapeOrientation; + break; + case DISPLAYCONFIG_ROTATION_ROTATE270: + data->orientation = Qt::InvertedPortraitOrientation; + break; + case DISPLAYCONFIG_ROTATION_FORCE_UINT32: + Q_UNREACHABLE(); + break; + } + if (pathInfo.targetInfo.refreshRate.Numerator && pathInfo.targetInfo.refreshRate.Denominator) + data->refreshRateHz = static_cast<qreal>(pathInfo.targetInfo.refreshRate.Numerator) + / pathInfo.targetInfo.refreshRate.Denominator; + } else { + data->orientation = data->geometry.height() > data->geometry.width() + ? Qt::PortraitOrientation + : Qt::LandscapeOrientation; + } // EnumDisplayMonitors (as opposed to EnumDisplayDevices) enumerates only // virtual desktop screens. data->flags |= QWindowsScreenData::VirtualDesktop; @@ -116,12 +336,22 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data) return true; } -// from QDesktopWidget, taking WindowsScreenDataList as LPARAM +// from monitorData, taking WindowsScreenDataList as LPARAM BOOL QT_WIN_CALLBACK monitorEnumCallback(HMONITOR hMonitor, HDC, LPRECT, LPARAM p) { QWindowsScreenData data; if (monitorData(hMonitor, &data)) { auto *result = reinterpret_cast<WindowsScreenDataList *>(p); + auto it = std::find_if(result->rbegin(), result->rend(), + [&data](QWindowsScreenData i){ return i.name == data.name; }); + if (it != result->rend()) { + int previousIndex = 1; + if (it->deviceIndex.has_value()) + previousIndex = it->deviceIndex.value(); + else + (*it).deviceIndex = 1; + data.deviceIndex = previousIndex + 1; + } // QWindowSystemInterface::handleScreenAdded() documentation specifies that first // added screen will be the primary screen, so order accordingly. // Note that the side effect of this policy is that there is no way to change primary @@ -148,14 +378,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; + 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) @@ -171,7 +401,6 @@ static QDebug operator<<(QDebug dbg, const QWindowsScreenData &d) \brief Windows screen. \sa QWindowsScreenManager \internal - \ingroup qt-lighthouse-win */ QWindowsScreen::QWindowsScreen(const QWindowsScreenData &data) : @@ -182,7 +411,12 @@ QWindowsScreen::QWindowsScreen(const QWindowsScreenData &data) : { } -Q_GUI_EXPORT QPixmap qt_pixmapFromWinHBITMAP(HBITMAP bitmap, int hbitmapFormat = 0); +QString QWindowsScreen::name() const +{ + return m_data.deviceIndex.has_value() + ? (u"%1 (%2)"_s).arg(m_data.name, QString::number(m_data.deviceIndex.value())) + : m_data.name; +} QPixmap QWindowsScreen::grabWindow(WId window, int xIn, int yIn, int width, int height) const { @@ -242,7 +476,7 @@ QWindow *QWindowsScreen::topLevelAt(const QPoint &point) const if (QWindow *child = QWindowsScreen::windowAt(point, CWP_SKIPINVISIBLE)) result = QWindowsWindow::topLevelOf(child); if (QWindowsContext::verbose > 1) - qCDebug(lcQpaWindows) <<__FUNCTION__ << point << result; + qCDebug(lcQpaScreen) <<__FUNCTION__ << point << result; return result; } @@ -253,7 +487,7 @@ QWindow *QWindowsScreen::windowAt(const QPoint &screenPoint, unsigned flags) findPlatformWindowAt(GetDesktopWindow(), screenPoint, flags)) result = bw->window(); if (QWindowsContext::verbose > 1) - qCDebug(lcQpaWindows) <<__FUNCTION__ << screenPoint << " returns " << result; + qCDebug(lcQpaScreen) <<__FUNCTION__ << screenPoint << " returns " << result; return result; } @@ -289,7 +523,7 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData) m_data.physicalSizeMM = newData.physicalSizeMM; if (m_data.hMonitor != newData.hMonitor) { - qCDebug(lcQpaWindows) << "Monitor" << m_data.name + qCDebug(lcQpaScreen) << "Monitor" << m_data.name << "has had its hMonitor handle changed from" << m_data.hMonitor << "to" << newData.hMonitor; m_data.hMonitor = newData.hMonitor; @@ -302,10 +536,14 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData) const bool dpiChanged = !qFuzzyCompare(m_data.dpi.first, newData.dpi.first) || !qFuzzyCompare(m_data.dpi.second, newData.dpi.second); const bool orientationChanged = m_data.orientation != newData.orientation; + const bool primaryChanged = (newData.flags & QWindowsScreenData::PrimaryScreen) + && !(m_data.flags & QWindowsScreenData::PrimaryScreen); m_data.dpi = newData.dpi; m_data.orientation = newData.orientation; m_data.geometry = newData.geometry; m_data.availableGeometry = newData.availableGeometry; + m_data.flags = (m_data.flags & ~QWindowsScreenData::PrimaryScreen) + | (newData.flags & QWindowsScreenData::PrimaryScreen); if (dpiChanged) { QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), @@ -318,6 +556,8 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData) QWindowSystemInterface::handleScreenGeometryChange(screen(), newData.geometry, newData.availableGeometry); } + if (primaryChanged) + QWindowSystemInterface::handlePrimaryScreenChanged(this); } HMONITOR QWindowsScreen::handle() const @@ -334,62 +574,50 @@ QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScre return result; } -enum OrientationPreference : DWORD // matching Win32 API ORIENTATION_PREFERENCE -{ - orientationPreferenceNone = 0, - orientationPreferenceLandscape = 0x1, - orientationPreferencePortrait = 0x2, - orientationPreferenceLandscapeFlipped = 0x4, - orientationPreferencePortraitFlipped = 0x8 -}; - bool QWindowsScreen::setOrientationPreference(Qt::ScreenOrientation o) { bool result = false; - if (QWindowsContext::user32dll.setDisplayAutoRotationPreferences) { - DWORD orientationPreference = 0; - switch (o) { - case Qt::PrimaryOrientation: - orientationPreference = orientationPreferenceNone; - break; - case Qt::PortraitOrientation: - orientationPreference = orientationPreferencePortrait; - break; - case Qt::LandscapeOrientation: - orientationPreference = orientationPreferenceLandscape; - break; - case Qt::InvertedPortraitOrientation: - orientationPreference = orientationPreferencePortraitFlipped; - break; - case Qt::InvertedLandscapeOrientation: - orientationPreference = orientationPreferenceLandscapeFlipped; - break; - } - result = QWindowsContext::user32dll.setDisplayAutoRotationPreferences(orientationPreference); + ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE; + switch (o) { + case Qt::PrimaryOrientation: + break; + case Qt::PortraitOrientation: + orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT; + break; + case Qt::LandscapeOrientation: + orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE; + break; + case Qt::InvertedPortraitOrientation: + orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED; + break; + case Qt::InvertedLandscapeOrientation: + orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED; + break; } + result = SetDisplayAutoRotationPreferences(orientationPreference); return result; } Qt::ScreenOrientation QWindowsScreen::orientationPreference() { Qt::ScreenOrientation result = Qt::PrimaryOrientation; - if (QWindowsContext::user32dll.getDisplayAutoRotationPreferences) { - DWORD orientationPreference = 0; - if (QWindowsContext::user32dll.getDisplayAutoRotationPreferences(&orientationPreference)) { - switch (orientationPreference) { - case orientationPreferenceLandscape: - result = Qt::LandscapeOrientation; - break; - case orientationPreferencePortrait: - result = Qt::PortraitOrientation; - break; - case orientationPreferenceLandscapeFlipped: - result = Qt::InvertedLandscapeOrientation; - break; - case orientationPreferencePortraitFlipped: - result = Qt::InvertedPortraitOrientation; - break; - } + ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE; + if (GetDisplayAutoRotationPreferences(&orientationPreference)) { + switch (orientationPreference) { + case ORIENTATION_PREFERENCE_NONE: + break; + case ORIENTATION_PREFERENCE_LANDSCAPE: + result = Qt::LandscapeOrientation; + break; + case ORIENTATION_PREFERENCE_PORTRAIT: + result = Qt::PortraitOrientation; + break; + case ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED: + result = Qt::InvertedLandscapeOrientation; + break; + case ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED: + result = Qt::InvertedPortraitOrientation; + break; } } return result; @@ -402,9 +630,9 @@ QPlatformScreen::SubpixelAntialiasingType QWindowsScreen::subpixelAntialiasingTy { QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint(); if (type == QPlatformScreen::Subpixel_None) { - QSettings settings(QLatin1String(R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Avalon.Graphics\DISPLAY1)"), + QSettings settings(R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Avalon.Graphics\DISPLAY1)"_L1, QSettings::NativeFormat); - int registryValue = settings.value(QLatin1String("PixelStructure"), -1).toInt(); + int registryValue = settings.value("PixelStructure"_L1, -1).toInt(); switch (registryValue) { case 0: type = QPlatformScreen::Subpixel_None; @@ -432,55 +660,68 @@ QPlatformScreen::SubpixelAntialiasingType QWindowsScreen::subpixelAntialiasingTy \sa QWindowsScreen \internal - \ingroup qt-lighthouse-win */ -QWindowsScreenManager::QWindowsScreenManager() = default; +extern "C" LRESULT QT_WIN_CALLBACK qDisplayChangeObserverWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == WM_DISPLAYCHANGE) { + qCDebug(lcQpaScreen) << "Handling WM_DISPLAYCHANGE"; + if (QWindowsTheme *t = QWindowsTheme::instance()) + t->displayChanged(); + QWindowsWindow::displayChanged(); + if (auto *context = QWindowsContext::instance()) + context->screenManager().handleScreenChanges(); + } + return DefWindowProc(hwnd, message, wParam, lParam); +} -bool QWindowsScreenManager::isSingleScreen() +QWindowsScreenManager::QWindowsScreenManager() = default; + +void QWindowsScreenManager::initialize() { - return QWindowsContext::instance()->screenManager().screens().size() < 2; -} + qCDebug(lcQpaScreen) << "Initializing screen manager"; -/*! - \brief Triggers synchronization of screens (WM_DISPLAYCHANGE). + auto className = QWindowsContext::instance()->registerWindowClass( + QWindowsContext::classNamePrefix() + QLatin1String("ScreenChangeObserverWindow"), + qDisplayChangeObserverWndProc); - Subsequent events are compressed since WM_DISPLAYCHANGE is sent to - each top level window. -*/ + // HWND_MESSAGE windows do not get WM_DISPLAYCHANGE, so we need to create + // a real top level window that we never show. + m_displayChangeObserver = CreateWindowEx(0, reinterpret_cast<LPCWSTR>(className.utf16()), + nullptr, WS_TILED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + Q_ASSERT(m_displayChangeObserver); -bool QWindowsScreenManager::handleDisplayChange(WPARAM wParam, LPARAM lParam) -{ - const int newDepth = int(wParam); - const WORD newHorizontalResolution = LOWORD(lParam); - const WORD newVerticalResolution = HIWORD(lParam); - if (newDepth != m_lastDepth || newHorizontalResolution != m_lastHorizontalResolution - || newVerticalResolution != m_lastVerticalResolution) { - m_lastDepth = newDepth; - m_lastHorizontalResolution = newHorizontalResolution; - m_lastVerticalResolution = newVerticalResolution; - qCDebug(lcQpaWindows) << __FUNCTION__ << "Depth=" << newDepth - << ", resolution " << newHorizontalResolution << 'x' << newVerticalResolution; - handleScreenChanges(); - } - return false; + qCDebug(lcQpaScreen) << "Created display change observer" << m_displayChangeObserver; + + handleScreenChanges(); +} + +QWindowsScreenManager::~QWindowsScreenManager() +{ + DestroyWindow(m_displayChangeObserver); +} + +bool QWindowsScreenManager::isSingleScreen() +{ + return QWindowsContext::instance()->screenManager().screens().size() < 2; } static inline int indexOfMonitor(const QWindowsScreenManager::WindowsScreenList &screens, - const QString &monitorName) + const QString &deviceName) { for (int i= 0; i < screens.size(); ++i) - if (screens.at(i)->data().name == monitorName) + if (screens.at(i)->data().deviceName == deviceName) return i; return -1; } static inline int indexOfMonitor(const WindowsScreenDataList &screenData, - const QString &monitorName) + const QString &deviceName) { for (int i = 0; i < screenData.size(); ++i) - if (screenData.at(i).name == monitorName) + if (screenData.at(i).deviceName == deviceName) return i; return -1; } @@ -504,7 +745,7 @@ static void moveToVirtualScreen(QWindow *w, const QScreen *newScreen) void QWindowsScreenManager::removeScreen(int index) { - qCDebug(lcQpaWindows) << "Removing Monitor:" << m_screens.at(index)->data(); + qCDebug(lcQpaScreen) << "Removing Monitor:" << m_screens.at(index)->data(); QScreen *screen = m_screens.at(index)->screen(); QScreen *primaryScreen = QGuiApplication::primaryScreen(); // QTBUG-38650: When a screen is disconnected, Windows will automatically @@ -546,7 +787,7 @@ bool QWindowsScreenManager::handleScreenChanges() const bool lockScreen = newDataList.size() == 1 && (newDataList.front().flags & QWindowsScreenData::LockScreen); bool primaryScreenChanged = false; for (const QWindowsScreenData &newData : newDataList) { - const int existingIndex = indexOfMonitor(m_screens, newData.name); + const int existingIndex = indexOfMonitor(m_screens, newData.deviceName); if (existingIndex != -1) { m_screens.at(existingIndex)->handleChanges(newData); if (existingIndex == 0) @@ -556,19 +797,21 @@ bool QWindowsScreenManager::handleScreenChanges() m_screens.push_back(newScreen); QWindowSystemInterface::handleScreenAdded(newScreen, newData.flags & QWindowsScreenData::PrimaryScreen); - qCDebug(lcQpaWindows) << "New Monitor: " << newData; + qCDebug(lcQpaScreen) << "New Monitor: " << newData; } // exists } // for new screens. // Remove deleted ones but keep main monitors if we get only the // temporary lock screen to avoid window recreation (QTBUG-33062). if (!lockScreen) { for (int i = m_screens.size() - 1; i >= 0; --i) { - if (indexOfMonitor(newDataList, m_screens.at(i)->data().name) == -1) + if (indexOfMonitor(newDataList, m_screens.at(i)->data().deviceName) == -1) removeScreen(i); } // for existing screens } // not lock screen - if (primaryScreenChanged) - QWindowsTheme::instance()->refreshFonts(); + if (primaryScreenChanged) { + if (auto theme = QWindowsTheme::instance()) // QTBUG-85734/Wine + theme->refreshFonts(); + } return true; } |