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