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