Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsscreen.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
10#include "qwindowstheme.h"
12
13#include <QtCore/qt_windows.h>
14
15#include <QtCore/qsettings.h>
16#include <QtGui/qpixmap.h>
17#include <QtGui/qguiapplication.h>
18#include <qpa/qwindowsysteminterface.h>
19#include <QtCore/private/qsystemerror_p.h>
20#include <QtGui/private/qedidparser_p.h>
21#include <private/qhighdpiscaling_p.h>
22#include <private/qwindowsfontdatabasebase_p.h>
23#include <private/qpixmap_win_p.h>
24#include <private/quniquehandle_p.h>
25
26#include <QtGui/qscreen.h>
27
28#include <QtCore/qdebug.h>
29
30#include <memory>
31#include <type_traits>
32
33#include <cfgmgr32.h>
34#include <setupapi.h>
35#include <shellscalingapi.h>
36
37QT_BEGIN_NAMESPACE
38
39using namespace Qt::StringLiterals;
40
41static inline QDpi deviceDPI(HDC hdc)
42{
43 return QDpi(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY));
44}
45
46static inline QDpi monitorDPI(HMONITOR hMonitor)
47{
48 UINT dpiX;
49 UINT dpiY;
50 if (SUCCEEDED(GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)))
51 return QDpi(dpiX, dpiY);
52 return {0, 0};
53}
54
55static std::vector<DISPLAYCONFIG_PATH_INFO> getPathInfo(const MONITORINFOEX &viewInfo)
56{
57 // We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO.
58 std::vector<DISPLAYCONFIG_PATH_INFO> pathInfos;
59 std::vector<DISPLAYCONFIG_MODE_INFO> modeInfos;
60
61 // Fetch paths
62 LONG result;
63 UINT32 numPathArrayElements;
64 UINT32 numModeInfoArrayElements;
65 do {
66 // QueryDisplayConfig documentation doesn't say the number of needed elements is updated
67 // when the call fails with ERROR_INSUFFICIENT_BUFFER, so we need a separate call to
68 // look up the needed buffer sizes.
69 if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements,
70 &numModeInfoArrayElements) != ERROR_SUCCESS) {
71 return {};
72 }
73 pathInfos.resize(numPathArrayElements);
74 modeInfos.resize(numModeInfoArrayElements);
75 result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements, pathInfos.data(),
76 &numModeInfoArrayElements, modeInfos.data(), nullptr);
77 } while (result == ERROR_INSUFFICIENT_BUFFER);
78
79 if (result != ERROR_SUCCESS)
80 return {};
81
82 // Find paths matching monitor name
83 auto discardThese =
84 std::remove_if(pathInfos.begin(), pathInfos.end(), [&](const auto &path) -> bool {
85 DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName;
86 deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
87 deviceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
88 deviceName.header.adapterId = path.sourceInfo.adapterId;
89 deviceName.header.id = path.sourceInfo.id;
90 if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
91 return wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) != 0;
92 }
93 return true;
94 });
95
96 pathInfos.erase(discardThese, pathInfos.end());
97
98 return pathInfos;
99}
100
101#if 0
102// Needed later for HDR support
103static float getMonitorSDRWhiteLevel(DISPLAYCONFIG_PATH_TARGET_INFO *targetInfo)
104{
105 const float defaultSdrWhiteLevel = 200.0;
106 if (!targetInfo)
107 return defaultSdrWhiteLevel;
108
109 DISPLAYCONFIG_SDR_WHITE_LEVEL whiteLevel = {};
110 whiteLevel.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
111 whiteLevel.header.size = sizeof(DISPLAYCONFIG_SDR_WHITE_LEVEL);
112 whiteLevel.header.adapterId = targetInfo->adapterId;
113 whiteLevel.header.id = targetInfo->id;
114 if (DisplayConfigGetDeviceInfo(&whiteLevel.header) != ERROR_SUCCESS)
115 return defaultSdrWhiteLevel;
116 return whiteLevel.SDRWhiteLevel * 80.0 / 1000.0;
117}
118#endif
119
120using WindowsScreenDataList = QList<QWindowsScreenData>;
121
122namespace {
123
124struct DiRegKeyHandleTraits
125{
126 using Type = HKEY;
127 static Type invalidValue() noexcept
128 {
129 // The setupapi.h functions return INVALID_HANDLE_VALUE when failing to open a registry key
130 return reinterpret_cast<HKEY>(INVALID_HANDLE_VALUE);
131 }
132 static bool close(Type handle) noexcept { return RegCloseKey(handle) == ERROR_SUCCESS; }
133};
134
135using DiRegKeyHandle = QUniqueHandle<DiRegKeyHandleTraits>;
136
137struct DevInfoHandleTraits
138{
139 using Type = HDEVINFO;
140 static Type invalidValue() noexcept
141 {
142 return reinterpret_cast<HDEVINFO>(INVALID_HANDLE_VALUE);
143 }
144 static bool close(Type handle) noexcept { return SetupDiDestroyDeviceInfoList(handle) == TRUE; }
145};
146
147using DevInfoHandle = QUniqueHandle<DevInfoHandleTraits>;
148
149}
150
151static void setMonitorDataFromSetupApi(QWindowsScreenData &data,
152 const std::vector<DISPLAYCONFIG_PATH_INFO> &pathGroup)
153{
154 if (pathGroup.empty()) {
155 return;
156 }
157
158 // The only property shared among monitors in a clone group is deviceName
159 {
160 DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
161 deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
162 deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
163 // The first element in the clone group is the main monitor.
164 deviceName.header.adapterId = pathGroup[0].targetInfo.adapterId;
165 deviceName.header.id = pathGroup[0].targetInfo.id;
166 if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
167 data.devicePath = QString::fromWCharArray(deviceName.monitorDevicePath);
168 } else {
169 qCWarning(lcQpaScreen)
170 << u"Unable to get device information for %1:"_s.arg(pathGroup[0].targetInfo.id)
171 << QSystemError::windowsString();
172 }
173 }
174
175 // The rest must be concatenated into the resulting property
176 QStringList names;
177 QStringList manufacturers;
178 QStringList models;
179 QStringList serialNumbers;
180
181 for (const auto &path : pathGroup) {
182 DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
183 deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
184 deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
185 deviceName.header.adapterId = path.targetInfo.adapterId;
186 deviceName.header.id = path.targetInfo.id;
187 if (DisplayConfigGetDeviceInfo(&deviceName.header) != ERROR_SUCCESS) {
188 qCWarning(lcQpaScreen)
189 << u"Unable to get device information for %1:"_s.arg(path.targetInfo.id)
190 << QSystemError::windowsString();
191 continue;
192 }
193
194 // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-monitor
195 constexpr GUID GUID_DEVINTERFACE_MONITOR = {
196 0xe6f07b5f, 0xee97, 0x4a90, { 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 }
197 };
198 const DevInfoHandle devInfo{ SetupDiGetClassDevs(
199 &GUID_DEVINTERFACE_MONITOR, nullptr, nullptr, DIGCF_DEVICEINTERFACE) };
200
201 if (!devInfo.isValid())
202 continue;
203
204 SP_DEVICE_INTERFACE_DATA deviceInterfaceData{};
205 deviceInterfaceData.cbSize = sizeof(deviceInterfaceData);
206
207 if (!SetupDiOpenDeviceInterfaceW(devInfo.get(), deviceName.monitorDevicePath, DIODI_NO_ADD,
208 &deviceInterfaceData)) {
209 qCWarning(lcQpaScreen)
210 << u"Unable to open monitor interface to %1:"_s.arg(data.deviceName)
211 << QSystemError::windowsString();
212 continue;
213 }
214
215 DWORD requiredSize{ 0 };
216 if (SetupDiGetDeviceInterfaceDetailW(devInfo.get(), &deviceInterfaceData, nullptr, 0,
217 &requiredSize, nullptr)
218 || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
219 continue;
220 }
221
222 const std::unique_ptr<std::byte[]> storage(new std::byte[requiredSize]);
223 auto *devicePath = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W *>(storage.get());
224 devicePath->cbSize = sizeof(std::remove_pointer_t<decltype(devicePath)>);
225 SP_DEVINFO_DATA deviceInfoData{};
226 deviceInfoData.cbSize = sizeof(deviceInfoData);
227 if (!SetupDiGetDeviceInterfaceDetailW(devInfo.get(), &deviceInterfaceData, devicePath,
228 requiredSize, nullptr, &deviceInfoData)) {
229 qCDebug(lcQpaScreen) << u"Unable to get monitor metadata for %1:"_s.arg(data.deviceName)
230 << QSystemError::windowsString();
231 continue;
232 }
233
234 const DiRegKeyHandle edidRegistryKey{ SetupDiOpenDevRegKey(
235 devInfo.get(), &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ) };
236
237 if (!edidRegistryKey.isValid())
238 continue;
239
240 DWORD edidDataSize{ 0 };
241 if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr, nullptr,
242 &edidDataSize)
243 != ERROR_SUCCESS) {
244 continue;
245 }
246
247 QByteArray edidData;
248 edidData.resize(edidDataSize);
249
250 if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr,
251 reinterpret_cast<unsigned char *>(edidData.data()), &edidDataSize)
252 != ERROR_SUCCESS) {
253 qCDebug(lcQpaScreen) << u"Unable to get EDID from the Registry for %1:"_s.arg(
254 data.deviceName)
255 << QSystemError::windowsString();
256 continue;
257 }
258
259 QEdidParser edid;
260
261 if (!edid.parse(edidData)) {
262 qCDebug(lcQpaScreen) << "Invalid EDID blob for" << data.deviceName;
263 continue;
264 }
265
266 // We skip edid.identifier because it is unreliable, and a better option
267 // is already available through DisplayConfigGetDeviceInfo (see below).
268 names << QString::fromWCharArray(deviceName.monitorFriendlyDeviceName);
269 manufacturers << edid.manufacturer;
270 models << edid.model;
271 serialNumbers << edid.serialNumber;
272 }
273
274 data.name = names.join(u"|"_s);
275 data.manufacturer = manufacturers.join(u"|"_s);
276 data.model = models.join(u"|"_s);
277 data.serialNumber = serialNumbers.join(u"|"_s);
278}
279
280static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
281{
282 MONITORINFOEX info;
283 memset(&info, 0, sizeof(MONITORINFOEX));
284 info.cbSize = sizeof(MONITORINFOEX);
285 if (GetMonitorInfo(hMonitor, &info) == FALSE)
286 return false;
287
288 data->hMonitor = hMonitor;
289 data->geometry = QRect(QPoint(info.rcMonitor.left, info.rcMonitor.top), QPoint(info.rcMonitor.right - 1, info.rcMonitor.bottom - 1));
290 data->availableGeometry = QRect(QPoint(info.rcWork.left, info.rcWork.top), QPoint(info.rcWork.right - 1, info.rcWork.bottom - 1));
291 data->deviceName = QString::fromWCharArray(info.szDevice);
292 const auto pathGroup = getPathInfo(info);
293 if (!pathGroup.empty()) {
294 setMonitorDataFromSetupApi(*data, pathGroup);
295 }
296 if (data->name.isEmpty())
297 data->name = data->deviceName;
298 if (data->deviceName == u"WinDisc") {
299 data->flags |= QWindowsScreenData::LockScreen;
300 } else {
301 if (const HDC hdc = CreateDC(info.szDevice, nullptr, nullptr, nullptr)) {
302 const QDpi dpi = monitorDPI(hMonitor);
303 data->dpi = dpi.first > 0 ? dpi : deviceDPI(hdc);
304 data->depth = GetDeviceCaps(hdc, BITSPIXEL);
305 data->format = data->depth == 16 ? QImage::Format_RGB16 : QImage::Format_RGB32;
306 data->physicalSizeMM = QSizeF(GetDeviceCaps(hdc, HORZSIZE), GetDeviceCaps(hdc, VERTSIZE));
307 const int refreshRate = GetDeviceCaps(hdc, VREFRESH);
308 if (refreshRate > 1) // 0,1 means hardware default.
309 data->refreshRateHz = refreshRate;
310 DeleteDC(hdc);
311 } else {
312 qWarning("%s: Unable to obtain handle for monitor '%s', defaulting to %g DPI.",
313 __FUNCTION__, qPrintable(data->deviceName),
314 data->dpi.first);
315 } // CreateDC() failed
316 } // not lock screen
317
318 // ### We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO,
319 // if we are going to use DISPLAYCONFIG lookups more.
320 if (!pathGroup.empty()) {
321 // The first element in the clone group is the main monitor.
322 const auto &pathInfo = pathGroup[0];
323 switch (pathInfo.targetInfo.rotation) {
324 case DISPLAYCONFIG_ROTATION_IDENTITY:
325 data->orientation = Qt::LandscapeOrientation;
326 break;
327 case DISPLAYCONFIG_ROTATION_ROTATE90:
328 data->orientation = Qt::PortraitOrientation;
329 break;
330 case DISPLAYCONFIG_ROTATION_ROTATE180:
331 data->orientation = Qt::InvertedLandscapeOrientation;
332 break;
333 case DISPLAYCONFIG_ROTATION_ROTATE270:
334 data->orientation = Qt::InvertedPortraitOrientation;
335 break;
336 case DISPLAYCONFIG_ROTATION_FORCE_UINT32:
337 Q_UNREACHABLE();
338 break;
339 }
340 if (pathInfo.targetInfo.refreshRate.Numerator && pathInfo.targetInfo.refreshRate.Denominator)
341 data->refreshRateHz = static_cast<qreal>(pathInfo.targetInfo.refreshRate.Numerator)
342 / pathInfo.targetInfo.refreshRate.Denominator;
343 } else {
344 data->orientation = data->geometry.height() > data->geometry.width()
345 ? Qt::PortraitOrientation
346 : Qt::LandscapeOrientation;
347 }
348 // EnumDisplayMonitors (as opposed to EnumDisplayDevices) enumerates only
349 // virtual desktop screens.
350 data->flags |= QWindowsScreenData::VirtualDesktop;
351 if (info.dwFlags & MONITORINFOF_PRIMARY)
352 data->flags |= QWindowsScreenData::PrimaryScreen;
353 return true;
354}
355
356// from monitorData, taking WindowsScreenDataList as LPARAM
357BOOL QT_WIN_CALLBACK monitorEnumCallback(HMONITOR hMonitor, HDC, LPRECT, LPARAM p)
358{
359 QWindowsScreenData data;
360 if (monitorData(hMonitor, &data)) {
361 auto *result = reinterpret_cast<WindowsScreenDataList *>(p);
362 auto it = std::find_if(result->rbegin(), result->rend(),
363 [&data](QWindowsScreenData i){ return i.name == data.name; });
364 if (it != result->rend()) {
365 int previousIndex = 1;
366 if (it->deviceIndex.has_value())
367 previousIndex = it->deviceIndex.value();
368 else
369 (*it).deviceIndex = 1;
370 data.deviceIndex = previousIndex + 1;
371 }
372 // QWindowSystemInterface::handleScreenAdded() documentation specifies that first
373 // added screen will be the primary screen, so order accordingly.
374 // Note that the side effect of this policy is that there is no way to change primary
375 // screen reported by Qt, unless we want to delete all existing screens and add them
376 // again whenever primary screen changes.
377 if (data.flags & QWindowsScreenData::PrimaryScreen)
378 result->prepend(data);
379 else
380 result->append(data);
381 }
382 return TRUE;
383}
384
386{
387 WindowsScreenDataList result;
388 EnumDisplayMonitors(nullptr, nullptr, monitorEnumCallback, reinterpret_cast<LPARAM>(&result));
389 return result;
390}
391
392#ifndef QT_NO_DEBUG_STREAM
393static QDebug operator<<(QDebug dbg, const QWindowsScreenData &d)
394{
395 QDebugStateSaver saver(dbg);
396 dbg.nospace();
397 dbg.noquote();
398 dbg << "Screen \"" << d.name << "\" " << d.geometry.width() << 'x' << d.geometry.height() << '+'
399 << d.geometry.x() << '+' << d.geometry.y() << " avail: " << d.availableGeometry.width()
400 << 'x' << d.availableGeometry.height() << '+' << d.availableGeometry.x() << '+'
401 << d.availableGeometry.y() << " physical: " << d.physicalSizeMM.width() << 'x'
402 << d.physicalSizeMM.height() << " DPI: " << d.dpi.first << 'x' << d.dpi.second
403 << " Depth: " << d.depth << " Format: " << d.format << " hMonitor: " << d.hMonitor
404 << " device name: " << d.deviceName << " manufacturer: " << d.manufacturer
405 << " model: " << d.model << " serial number: " << d.serialNumber;
406 if (d.flags & QWindowsScreenData::PrimaryScreen)
407 dbg << " primary";
408 if (d.flags & QWindowsScreenData::VirtualDesktop)
409 dbg << " virtual desktop";
410 if (d.flags & QWindowsScreenData::LockScreen)
411 dbg << " lock screen";
412 return dbg;
413}
414#endif // !QT_NO_DEBUG_STREAM
415
416/*!
417 \class QWindowsScreen
418 \brief Windows screen.
419 \sa QWindowsScreenManager
420 \internal
421*/
422
423QWindowsScreen::QWindowsScreen(const QWindowsScreenData &data) :
424 m_data(data)
425#ifndef QT_NO_CURSOR
426 , m_cursor(new QWindowsCursor(this))
427#endif
428{
429}
430
432{
433 return m_data.deviceIndex.has_value()
434 ? (u"%1 (%2)"_s).arg(m_data.name, QString::number(m_data.deviceIndex.value()))
435 : m_data.name;
436}
437
438QPixmap QWindowsScreen::grabWindow(WId window, int xIn, int yIn, int width, int height) const
439{
440 QSize windowSize;
441 int x = xIn;
442 int y = yIn;
443 HWND hwnd = reinterpret_cast<HWND>(window);
444 if (hwnd) {
445 RECT r;
446 GetClientRect(hwnd, &r);
447 windowSize = QSize(r.right - r.left, r.bottom - r.top);
448 } else {
449 // Grab current screen. The client rectangle of GetDesktopWindow() is the
450 // primary screen, but it is possible to grab other screens from it.
451 hwnd = GetDesktopWindow();
452 const QRect screenGeometry = geometry();
453 windowSize = screenGeometry.size();
454 // When dpi awareness is not set to PerMonitor, windows reports primary display or dummy
455 // DPI for all displays, so xIn and yIn and windowSize are calculated with a wrong DPI,
456 // so we need to recalculate them using the actual screen size we get from
457 // EnumDisplaySettings api.
459 if (dpiAwareness != QtWindows::DpiAwareness::PerMonitor &&
460 dpiAwareness != QtWindows::DpiAwareness::PerMonitorVersion2) {
461 MONITORINFOEX info = {};
462 info.cbSize = sizeof(MONITORINFOEX);
463 if (GetMonitorInfo(handle(), &info)) {
464 DEVMODE dm = {};
465 dm.dmSize = sizeof(dm);
466 if (EnumDisplaySettings(info.szDevice, ENUM_CURRENT_SETTINGS, &dm)) {
467 qreal scale = static_cast<qreal>(dm.dmPelsWidth) / windowSize.width();
468 x = static_cast<int>(static_cast<qreal>(x) * scale);
469 y = static_cast<int>(static_cast<qreal>(y) * scale);
470 windowSize = QSize(dm.dmPelsWidth, dm.dmPelsHeight);
471 }
472 }
473 }
474 x += screenGeometry.x();
475 y += screenGeometry.y();
476 }
477
478 if (width < 0)
479 width = windowSize.width() - xIn;
480 if (height < 0)
481 height = windowSize.height() - yIn;
482
483 // Create and setup bitmap
484 HDC display_dc = GetDC(nullptr);
485 HDC bitmap_dc = CreateCompatibleDC(display_dc);
486 HBITMAP bitmap = CreateCompatibleBitmap(display_dc, width, height);
487 HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);
488
489 // copy data
490 HDC window_dc = GetDC(hwnd);
491 BitBlt(bitmap_dc, 0, 0, width, height, window_dc, x, y, SRCCOPY | CAPTUREBLT);
492
493 // clean up all but bitmap
494 ReleaseDC(hwnd, window_dc);
495 SelectObject(bitmap_dc, null_bitmap);
496 DeleteDC(bitmap_dc);
497
498 const QPixmap pixmap = qt_pixmapFromWinHBITMAP(bitmap);
499
500 DeleteObject(bitmap);
501 ReleaseDC(nullptr, display_dc);
502
503 return pixmap;
504}
505
506/*!
507 \brief Find a top level window taking the flags of ChildWindowFromPointEx.
508*/
509
510QWindow *QWindowsScreen::topLevelAt(const QPoint &point) const
511{
512 QWindow *result = nullptr;
513 if (QWindow *child = QWindowsScreen::windowAt(point, CWP_SKIPINVISIBLE))
514 result = QWindowsWindow::topLevelOf(child);
516 qCDebug(lcQpaScreen) <<__FUNCTION__ << point << result;
517 return result;
518}
519
520QWindow *QWindowsScreen::windowAt(const QPoint &screenPoint, unsigned flags)
521{
522 QWindow* result = nullptr;
523 if (QPlatformWindow *bw = QWindowsContext::instance()->
524 findPlatformWindowAt(GetDesktopWindow(), screenPoint, flags))
525 result = bw->window();
527 qCDebug(lcQpaScreen) <<__FUNCTION__ << screenPoint << " returns " << result;
528 return result;
529}
530
531/*!
532 \brief Determine siblings in a virtual desktop system.
533
534 Self is by definition a sibling, else collect all screens
535 within virtual desktop.
536*/
537
539{
540 QList<QPlatformScreen *> result;
541 if (m_data.flags & QWindowsScreenData::VirtualDesktop) {
542 const QWindowsScreenManager::WindowsScreenList screens
544 for (QWindowsScreen *screen : screens) {
545 if (screen->data().flags & QWindowsScreenData::VirtualDesktop)
546 result.push_back(screen);
547 }
548 } else {
549 result.push_back(const_cast<QWindowsScreen *>(this));
550 }
551 return result;
552}
553
554/*!
555 \brief Notify QWindowSystemInterface about changes of a screen and synchronize data.
556*/
557
558void QWindowsScreen::handleChanges(const QWindowsScreenData &newData)
559{
560 m_data.physicalSizeMM = newData.physicalSizeMM;
561
562 if (m_data.hMonitor != newData.hMonitor) {
563 qCDebug(lcQpaScreen) << "Monitor" << m_data.name
564 << "has had its hMonitor handle changed from"
565 << m_data.hMonitor << "to" << newData.hMonitor;
566 m_data.hMonitor = newData.hMonitor;
567 }
568
569 // QGuiApplicationPrivate::processScreenGeometryChange() checks and emits
570 // DPI and orientation as well, so, assign new values and emit DPI first.
571 const bool geometryChanged = m_data.geometry != newData.geometry
572 || m_data.availableGeometry != newData.availableGeometry;
573 const bool dpiChanged = !qFuzzyCompare(m_data.dpi.first, newData.dpi.first)
574 || !qFuzzyCompare(m_data.dpi.second, newData.dpi.second);
575 const bool orientationChanged = m_data.orientation != newData.orientation;
576 const bool primaryChanged = (newData.flags & QWindowsScreenData::PrimaryScreen)
577 && !(m_data.flags & QWindowsScreenData::PrimaryScreen);
578 const bool refreshRateChanged = m_data.refreshRateHz != newData.refreshRateHz;
579 m_data.dpi = newData.dpi;
580 m_data.orientation = newData.orientation;
581 m_data.geometry = newData.geometry;
582 m_data.availableGeometry = newData.availableGeometry;
583 m_data.flags = (m_data.flags & ~QWindowsScreenData::PrimaryScreen)
584 | (newData.flags & QWindowsScreenData::PrimaryScreen);
585 m_data.refreshRateHz = newData.refreshRateHz;
586
587 if (dpiChanged) {
588 QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(),
589 newData.dpi.first,
590 newData.dpi.second);
591 }
592 if (orientationChanged)
593 QWindowSystemInterface::handleScreenOrientationChange(screen(), newData.orientation);
594 if (geometryChanged) {
595 QWindowSystemInterface::handleScreenGeometryChange(screen(),
596 newData.geometry, newData.availableGeometry);
597 }
598 if (primaryChanged)
599 QWindowSystemInterface::handlePrimaryScreenChanged(this);
600
601 if (refreshRateChanged)
602 QWindowSystemInterface::handleScreenRefreshRateChange(screen(), newData.refreshRateHz);
603}
604
606{
607 return m_data.hMonitor;
608}
609
610QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScreen::virtualGeometry()
611{
612 QRect result;
613 const auto siblings = screen->virtualSiblings();
614 for (const QPlatformScreen *sibling : siblings)
615 result |= sibling->geometry();
616 return result;
617}
618
619bool QWindowsScreen::setOrientationPreference(Qt::ScreenOrientation o)
620{
621 bool result = false;
622 ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
623 switch (o) {
624 case Qt::PrimaryOrientation:
625 break;
626 case Qt::PortraitOrientation:
627 orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT;
628 break;
629 case Qt::LandscapeOrientation:
630 orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE;
631 break;
632 case Qt::InvertedPortraitOrientation:
633 orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED;
634 break;
635 case Qt::InvertedLandscapeOrientation:
636 orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED;
637 break;
638 }
639 result = SetDisplayAutoRotationPreferences(orientationPreference);
640 return result;
641}
642
643Qt::ScreenOrientation QWindowsScreen::orientationPreference()
644{
645 Qt::ScreenOrientation result = Qt::PrimaryOrientation;
646 ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
647 if (GetDisplayAutoRotationPreferences(&orientationPreference)) {
648 switch (orientationPreference) {
649 case ORIENTATION_PREFERENCE_NONE:
650 break;
651 case ORIENTATION_PREFERENCE_LANDSCAPE:
652 result = Qt::LandscapeOrientation;
653 break;
654 case ORIENTATION_PREFERENCE_PORTRAIT:
655 result = Qt::PortraitOrientation;
656 break;
657 case ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED:
658 result = Qt::InvertedLandscapeOrientation;
659 break;
660 case ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED:
661 result = Qt::InvertedPortraitOrientation;
662 break;
663 }
664 }
665 return result;
666}
667
668/*!
669 \brief Queries ClearType settings to check the pixel layout
670*/
672{
673 QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint();
674 if (type == QPlatformScreen::Subpixel_None) {
675 QSettings settings(R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Avalon.Graphics\DISPLAY1)"_L1,
676 QSettings::NativeFormat);
677 int registryValue = settings.value("PixelStructure"_L1, -1).toInt();
678 switch (registryValue) {
679 case 0:
680 type = QPlatformScreen::Subpixel_None;
681 break;
682 case 1:
683 type = QPlatformScreen::Subpixel_RGB;
684 break;
685 case 2:
686 type = QPlatformScreen::Subpixel_BGR;
687 break;
688 default:
689 type = QPlatformScreen::Subpixel_None;
690 break;
691 }
692 }
693 return type;
694}
695
696/*!
697 \class QWindowsScreenManager
698 \brief Manages a list of QWindowsScreen.
699
700 Listens for changes and notifies QWindowSystemInterface about changed/
701 added/deleted screens.
702
703 \sa QWindowsScreen
704 \internal
705*/
706
707LRESULT QT_WIN_CALLBACK qDisplayChangeObserverWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
708{
709 if (message == WM_DISPLAYCHANGE) {
710 qCDebug(lcQpaScreen) << "Handling WM_DISPLAYCHANGE";
711 if (QWindowsTheme *t = QWindowsTheme::instance())
712 t->displayChanged();
713 QWindowsWindow::displayChanged();
714 if (auto *context = QWindowsContext::instance())
715 context->screenManager().handleScreenChanges();
716 }
717
718 return DefWindowProc(hwnd, message, wParam, lParam);
719}
720
722
724{
725 qCDebug(lcQpaScreen) << "Initializing screen manager";
726
727 auto className = QWindowsWindowClassRegistry::instance()->registerWindowClass(
728 "ScreenChangeObserverWindow"_L1,
729 qDisplayChangeObserverWndProc);
730
731 // HWND_MESSAGE windows do not get WM_DISPLAYCHANGE, so we need to create
732 // a real top level window that we never show.
733 m_displayChangeObserver = CreateWindowEx(0, reinterpret_cast<LPCWSTR>(className.utf16()),
734 nullptr, WS_TILED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
735 nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
736 Q_ASSERT(m_displayChangeObserver);
737
738 qCDebug(lcQpaScreen) << "Created display change observer" << m_displayChangeObserver;
739
741}
742
744{
745 qCDebug(lcQpaScreen) << "Destroying display change observer" << m_displayChangeObserver;
746 DestroyWindow(m_displayChangeObserver);
747 m_displayChangeObserver = nullptr;
748}
749
751
756
757static inline int indexOfMonitor(const QWindowsScreenManager::WindowsScreenList &screens,
758 const QString &deviceName)
759{
760 for (int i= 0; i < screens.size(); ++i)
761 if (screens.at(i)->data().deviceName == deviceName)
762 return i;
763 return -1;
764}
765
766static inline int indexOfMonitor(const WindowsScreenDataList &screenData,
767 const QString &deviceName)
768{
769 for (int i = 0; i < screenData.size(); ++i)
770 if (screenData.at(i).deviceName == deviceName)
771 return i;
772 return -1;
773}
774
775// Move a window to a new virtual screen, accounting for varying sizes.
776static void moveToVirtualScreen(QWindow *w, const QScreen *newScreen)
777{
778 QRect geometry = w->geometry();
779 const QRect oldScreenGeometry = w->screen()->geometry();
780 const QRect newScreenGeometry = newScreen->geometry();
781 QPoint relativePosition = geometry.topLeft() - oldScreenGeometry.topLeft();
782 if (oldScreenGeometry.size() != newScreenGeometry.size()) {
783 const qreal factor =
784 qreal(QPoint(newScreenGeometry.width(), newScreenGeometry.height()).manhattanLength()) /
785 qreal(QPoint(oldScreenGeometry.width(), oldScreenGeometry.height()).manhattanLength());
786 relativePosition = (QPointF(relativePosition) * factor).toPoint();
787 }
788 geometry.moveTopLeft(relativePosition);
789 w->setGeometry(geometry);
790}
791
792void QWindowsScreenManager::addScreen(const QWindowsScreenData &screenData)
793{
794 auto *newScreen = new QWindowsScreen(screenData);
795 m_screens.push_back(newScreen);
796 QWindowSystemInterface::handleScreenAdded(newScreen,
797 screenData.flags & QWindowsScreenData::PrimaryScreen);
798 qCDebug(lcQpaScreen) << "New Monitor: " << screenData;
799
800 // When a new screen is attached Window might move windows to the new screen
801 // automatically, in which case they will get a WM_DPICHANGED event. But at
802 // that point we have not received WM_DISPLAYCHANGE yet, so we fail to reflect
803 // the new screen's DPI. To account for this we explicitly check for screen
804 // change here, now that we are processing the WM_DISPLAYCHANGE.
805 const auto allWindows = QGuiApplication::allWindows();
806 for (QWindow *w : allWindows) {
807 if (w->isVisible() && w->handle()) {
808 if (QWindowsWindow *window = QWindowsWindow::windowsWindowOf(w))
809 window->checkForScreenChanged(QWindowsWindow::ScreenChangeMode::FromScreenAdded);
810 }
811 }
812}
813
814void QWindowsScreenManager::removeScreen(int index)
815{
816 qCDebug(lcQpaScreen) << "Removing Monitor:" << m_screens.at(index)->data();
817 QPlatformScreen *platformScreen = m_screens.takeAt(index);
818 QScreen *screen = platformScreen->screen();
819 QScreen *primaryScreen = QGuiApplication::primaryScreen();
820 // QTBUG-38650: When a screen is disconnected, Windows will automatically
821 // move the Window to another screen. This will trigger a geometry change
822 // event, but unfortunately after the screen destruction signal. To prevent
823 // QtGui from automatically hiding the QWindow, pretend all Windows move to
824 // the primary screen first (which is likely the correct, final screen).
825 // QTBUG-39320: Windows does not automatically move WS_EX_TOOLWINDOW (dock) windows;
826 // move those manually.
827 if (screen != primaryScreen) {
828 unsigned movedWindowCount = 0;
829 const QWindowList tlws = QGuiApplication::topLevelWindows();
830 for (QWindow *w : tlws) {
831 if (w->screen() == screen && w->handle()) {
832 if (w->isVisible() && w->windowState() != Qt::WindowMinimized
833 && (QWindowsWindow::baseWindowOf(w)->exStyle() & WS_EX_TOOLWINDOW)) {
834 moveToVirtualScreen(w, primaryScreen);
835 } else {
836 QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(w, primaryScreen);
837 }
838 ++movedWindowCount;
839 }
840 }
841 if (movedWindowCount)
842 QWindowSystemInterface::flushWindowSystemEvents();
843 }
844 QWindowSystemInterface::handleScreenRemoved(platformScreen);
845}
846
847/*!
848 \brief Synchronizes the screen list, adds new screens, removes deleted
849 ones and propagates resolution changes to QWindowSystemInterface.
850*/
851
853{
854 // Look for changed monitors, add new ones
855 const WindowsScreenDataList newDataList = monitorData();
856 const bool lockScreen = newDataList.size() == 1 && (newDataList.front().flags & QWindowsScreenData::LockScreen);
857 bool primaryScreenChanged = false;
858 for (const QWindowsScreenData &newData : newDataList) {
859 const int existingIndex = indexOfMonitor(m_screens, newData.deviceName);
860 if (existingIndex != -1) {
861 m_screens.at(existingIndex)->handleChanges(newData);
862 if (existingIndex == 0)
863 primaryScreenChanged = true;
864 } else {
865 addScreen(newData);
866 } // exists
867 } // for new screens.
868 // Remove deleted ones but keep main monitors if we get only the
869 // temporary lock screen to avoid window recreation (QTBUG-33062).
870 if (!lockScreen) {
871 for (int i = m_screens.size() - 1; i >= 0; --i) {
872 if (indexOfMonitor(newDataList, m_screens.at(i)->data().deviceName) == -1)
873 removeScreen(i);
874 } // for existing screens
875 } // not lock screen
876 if (primaryScreenChanged) {
877 if (auto theme = QWindowsTheme::instance()) // QTBUG-85734/Wine
878 theme->refreshFonts();
879 }
880 return true;
881}
882
884{
885 // Delete screens in reverse order to avoid crash in case of multiple screens
886 while (!m_screens.isEmpty())
887 QWindowSystemInterface::handleScreenRemoved(m_screens.takeLast());
888}
889
891{
892 for (QWindowsScreen *scr : m_screens) {
893 if (scr->geometry().contains(p))
894 return scr;
895 }
896 return nullptr;
897}
898
899const QWindowsScreen *QWindowsScreenManager::screenForMonitor(HMONITOR hMonitor) const
900{
901 if (hMonitor == nullptr)
902 return nullptr;
903 const auto it =
904 std::find_if(m_screens.cbegin(), m_screens.cend(),
905 [hMonitor](const QWindowsScreen *s)
906 {
907 return s->data().hMonitor == hMonitor
908 && (s->data().flags & QWindowsScreenData::VirtualDesktop) != 0;
909 });
910 return it != m_screens.cend() ? *it : nullptr;
911}
912
914{
915 HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
916 return screenForMonitor(hMonitor);
917}
918
920{
921 if (rect == nullptr)
922 return nullptr;
923 HMONITOR hMonitor = MonitorFromRect(rect, MONITOR_DEFAULTTONULL);
924 return screenForMonitor(hMonitor);
925}
926
927QT_END_NAMESPACE
\inmodule QtCore\reentrant
Definition qpoint.h:30
Singleton container for all relevant information.
QWindowsScreenManager & screenManager()
static QtWindows::DpiAwareness processDpiAwareness()
static QWindowsContext * instance()
Manages a list of QWindowsScreen.
bool handleScreenChanges()
Synchronizes the screen list, adds new screens, removes deleted ones and propagates resolution change...
const QWindowsScreen * screenForHwnd(HWND hwnd) const
const QWindowsScreen * screenAtDp(const QPoint &p) const
const QWindowsScreen * screenForRect(const RECT *rect) const
Windows screen.
QList< QPlatformScreen * > virtualSiblings() const override
Determine siblings in a virtual desktop system.
QPixmap grabWindow(WId window, int qX, int qY, int qWidth, int qHeight) const override
This function is called when Qt needs to be able to grab the content of a window.
QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const override
Queries ClearType settings to check the pixel layout.
QString name() const override
QWindow * topLevelAt(const QPoint &point) const override
Find a top level window taking the flags of ChildWindowFromPointEx.
HMONITOR handle() const override
static QWindowsTheme * instance()
static QWindowsWindowClassRegistry * instance()
static QDpi deviceDPI(HDC hdc)
static WindowsScreenDataList monitorData()
static std::vector< DISPLAYCONFIG_PATH_INFO > getPathInfo(const MONITORINFOEX &viewInfo)
static void moveToVirtualScreen(QWindow *w, const QScreen *newScreen)
static void setMonitorDataFromSetupApi(QWindowsScreenData &data, const std::vector< DISPLAYCONFIG_PATH_INFO > &pathGroup)
static int indexOfMonitor(const QWindowsScreenManager::WindowsScreenList &screens, const QString &deviceName)
static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
static QDpi monitorDPI(HMONITOR hMonitor)