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 m_data.dpi = newData.dpi;
557 m_data.orientation = newData.orientation;
558 m_data.geometry = newData.geometry;
559 m_data.availableGeometry = newData.availableGeometry;
560 m_data.flags = (m_data.flags & ~QWindowsScreenData::PrimaryScreen)
561 | (newData.flags & QWindowsScreenData::PrimaryScreen);
562
563 if (dpiChanged) {
564 QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(),
565 newData.dpi.first,
566 newData.dpi.second);
567 }
568 if (orientationChanged)
569 QWindowSystemInterface::handleScreenOrientationChange(screen(), newData.orientation);
570 if (geometryChanged) {
571 QWindowSystemInterface::handleScreenGeometryChange(screen(),
572 newData.geometry, newData.availableGeometry);
573 }
574 if (primaryChanged)
575 QWindowSystemInterface::handlePrimaryScreenChanged(this);
576}
577
579{
580 return m_data.hMonitor;
581}
582
583QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScreen::virtualGeometry()
584{
585 QRect result;
586 const auto siblings = screen->virtualSiblings();
587 for (const QPlatformScreen *sibling : siblings)
588 result |= sibling->geometry();
589 return result;
590}
591
592bool QWindowsScreen::setOrientationPreference(Qt::ScreenOrientation o)
593{
594 bool result = false;
595 ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
596 switch (o) {
597 case Qt::PrimaryOrientation:
598 break;
599 case Qt::PortraitOrientation:
600 orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT;
601 break;
602 case Qt::LandscapeOrientation:
603 orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE;
604 break;
605 case Qt::InvertedPortraitOrientation:
606 orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED;
607 break;
608 case Qt::InvertedLandscapeOrientation:
609 orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED;
610 break;
611 }
612 result = SetDisplayAutoRotationPreferences(orientationPreference);
613 return result;
614}
615
616Qt::ScreenOrientation QWindowsScreen::orientationPreference()
617{
618 Qt::ScreenOrientation result = Qt::PrimaryOrientation;
619 ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
620 if (GetDisplayAutoRotationPreferences(&orientationPreference)) {
621 switch (orientationPreference) {
622 case ORIENTATION_PREFERENCE_NONE:
623 break;
624 case ORIENTATION_PREFERENCE_LANDSCAPE:
625 result = Qt::LandscapeOrientation;
626 break;
627 case ORIENTATION_PREFERENCE_PORTRAIT:
628 result = Qt::PortraitOrientation;
629 break;
630 case ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED:
631 result = Qt::InvertedLandscapeOrientation;
632 break;
633 case ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED:
634 result = Qt::InvertedPortraitOrientation;
635 break;
636 }
637 }
638 return result;
639}
640
641/*!
642 \brief Queries ClearType settings to check the pixel layout
643*/
645{
646 QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint();
647 if (type == QPlatformScreen::Subpixel_None) {
648 QSettings settings(R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Avalon.Graphics\DISPLAY1)"_L1,
649 QSettings::NativeFormat);
650 int registryValue = settings.value("PixelStructure"_L1, -1).toInt();
651 switch (registryValue) {
652 case 0:
653 type = QPlatformScreen::Subpixel_None;
654 break;
655 case 1:
656 type = QPlatformScreen::Subpixel_RGB;
657 break;
658 case 2:
659 type = QPlatformScreen::Subpixel_BGR;
660 break;
661 default:
662 type = QPlatformScreen::Subpixel_None;
663 break;
664 }
665 }
666 return type;
667}
668
669/*!
670 \class QWindowsScreenManager
671 \brief Manages a list of QWindowsScreen.
672
673 Listens for changes and notifies QWindowSystemInterface about changed/
674 added/deleted screens.
675
676 \sa QWindowsScreen
677 \internal
678*/
679
680LRESULT QT_WIN_CALLBACK qDisplayChangeObserverWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
681{
682 if (message == WM_DISPLAYCHANGE) {
683 qCDebug(lcQpaScreen) << "Handling WM_DISPLAYCHANGE";
684 if (QWindowsTheme *t = QWindowsTheme::instance())
685 t->displayChanged();
686 QWindowsWindow::displayChanged();
687 if (auto *context = QWindowsContext::instance())
688 context->screenManager().handleScreenChanges();
689 }
690
691 return DefWindowProc(hwnd, message, wParam, lParam);
692}
693
695
697{
698 qCDebug(lcQpaScreen) << "Initializing screen manager";
699
700 auto className = QWindowsContext::instance()->registerWindowClass(
701 QWindowsContext::classNamePrefix() + QLatin1String("ScreenChangeObserverWindow"),
702 qDisplayChangeObserverWndProc);
703
704 // HWND_MESSAGE windows do not get WM_DISPLAYCHANGE, so we need to create
705 // a real top level window that we never show.
706 m_displayChangeObserver = CreateWindowEx(0, reinterpret_cast<LPCWSTR>(className.utf16()),
707 nullptr, WS_TILED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
708 nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
709 Q_ASSERT(m_displayChangeObserver);
710
711 qCDebug(lcQpaScreen) << "Created display change observer" << m_displayChangeObserver;
712
714}
715
717{
718 qCDebug(lcQpaScreen) << "Destroying display change observer" << m_displayChangeObserver;
719 DestroyWindow(m_displayChangeObserver);
720 m_displayChangeObserver = nullptr;
721}
722
724
729
730static inline int indexOfMonitor(const QWindowsScreenManager::WindowsScreenList &screens,
731 const QString &deviceName)
732{
733 for (int i= 0; i < screens.size(); ++i)
734 if (screens.at(i)->data().deviceName == deviceName)
735 return i;
736 return -1;
737}
738
739static inline int indexOfMonitor(const WindowsScreenDataList &screenData,
740 const QString &deviceName)
741{
742 for (int i = 0; i < screenData.size(); ++i)
743 if (screenData.at(i).deviceName == deviceName)
744 return i;
745 return -1;
746}
747
748// Move a window to a new virtual screen, accounting for varying sizes.
749static void moveToVirtualScreen(QWindow *w, const QScreen *newScreen)
750{
751 QRect geometry = w->geometry();
752 const QRect oldScreenGeometry = w->screen()->geometry();
753 const QRect newScreenGeometry = newScreen->geometry();
754 QPoint relativePosition = geometry.topLeft() - oldScreenGeometry.topLeft();
755 if (oldScreenGeometry.size() != newScreenGeometry.size()) {
756 const qreal factor =
757 qreal(QPoint(newScreenGeometry.width(), newScreenGeometry.height()).manhattanLength()) /
758 qreal(QPoint(oldScreenGeometry.width(), oldScreenGeometry.height()).manhattanLength());
759 relativePosition = (QPointF(relativePosition) * factor).toPoint();
760 }
761 geometry.moveTopLeft(relativePosition);
762 w->setGeometry(geometry);
763}
764
765void QWindowsScreenManager::addScreen(const QWindowsScreenData &screenData)
766{
767 auto *newScreen = new QWindowsScreen(screenData);
768 m_screens.push_back(newScreen);
769 QWindowSystemInterface::handleScreenAdded(newScreen,
770 screenData.flags & QWindowsScreenData::PrimaryScreen);
771 qCDebug(lcQpaScreen) << "New Monitor: " << screenData;
772
773 // When a new screen is attached Window might move windows to the new screen
774 // automatically, in which case they will get a WM_DPICHANGED event. But at
775 // that point we have not received WM_DISPLAYCHANGE yet, so we fail to reflect
776 // the new screen's DPI. To account for this we explicitly check for screen
777 // change here, now that we are processing the WM_DISPLAYCHANGE.
778 const auto allWindows = QGuiApplication::allWindows();
779 for (QWindow *w : allWindows) {
780 if (w->isVisible() && w->handle()) {
781 if (QWindowsWindow *window = QWindowsWindow::windowsWindowOf(w))
782 window->checkForScreenChanged(QWindowsWindow::ScreenChangeMode::FromScreenAdded);
783 }
784 }
785}
786
787void QWindowsScreenManager::removeScreen(int index)
788{
789 qCDebug(lcQpaScreen) << "Removing Monitor:" << m_screens.at(index)->data();
790 QPlatformScreen *platformScreen = m_screens.takeAt(index);
791 QScreen *screen = platformScreen->screen();
792 QScreen *primaryScreen = QGuiApplication::primaryScreen();
793 // QTBUG-38650: When a screen is disconnected, Windows will automatically
794 // move the Window to another screen. This will trigger a geometry change
795 // event, but unfortunately after the screen destruction signal. To prevent
796 // QtGui from automatically hiding the QWindow, pretend all Windows move to
797 // the primary screen first (which is likely the correct, final screen).
798 // QTBUG-39320: Windows does not automatically move WS_EX_TOOLWINDOW (dock) windows;
799 // move those manually.
800 if (screen != primaryScreen) {
801 unsigned movedWindowCount = 0;
802 const QWindowList tlws = QGuiApplication::topLevelWindows();
803 for (QWindow *w : tlws) {
804 if (w->screen() == screen && w->handle()) {
805 if (w->isVisible() && w->windowState() != Qt::WindowMinimized
806 && (QWindowsWindow::baseWindowOf(w)->exStyle() & WS_EX_TOOLWINDOW)) {
807 moveToVirtualScreen(w, primaryScreen);
808 } else {
809 QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(w, primaryScreen);
810 }
811 ++movedWindowCount;
812 }
813 }
814 if (movedWindowCount)
815 QWindowSystemInterface::flushWindowSystemEvents();
816 }
817 QWindowSystemInterface::handleScreenRemoved(platformScreen);
818}
819
820/*!
821 \brief Synchronizes the screen list, adds new screens, removes deleted
822 ones and propagates resolution changes to QWindowSystemInterface.
823*/
824
826{
827 // Look for changed monitors, add new ones
828 const WindowsScreenDataList newDataList = monitorData();
829 const bool lockScreen = newDataList.size() == 1 && (newDataList.front().flags & QWindowsScreenData::LockScreen);
830 bool primaryScreenChanged = false;
831 for (const QWindowsScreenData &newData : newDataList) {
832 const int existingIndex = indexOfMonitor(m_screens, newData.deviceName);
833 if (existingIndex != -1) {
834 m_screens.at(existingIndex)->handleChanges(newData);
835 if (existingIndex == 0)
836 primaryScreenChanged = true;
837 } else {
838 addScreen(newData);
839 } // exists
840 } // for new screens.
841 // Remove deleted ones but keep main monitors if we get only the
842 // temporary lock screen to avoid window recreation (QTBUG-33062).
843 if (!lockScreen) {
844 for (int i = m_screens.size() - 1; i >= 0; --i) {
845 if (indexOfMonitor(newDataList, m_screens.at(i)->data().deviceName) == -1)
846 removeScreen(i);
847 } // for existing screens
848 } // not lock screen
849 if (primaryScreenChanged) {
850 if (auto theme = QWindowsTheme::instance()) // QTBUG-85734/Wine
851 theme->refreshFonts();
852 }
853 return true;
854}
855
857{
858 // Delete screens in reverse order to avoid crash in case of multiple screens
859 while (!m_screens.isEmpty())
860 QWindowSystemInterface::handleScreenRemoved(m_screens.takeLast());
861}
862
864{
865 for (QWindowsScreen *scr : m_screens) {
866 if (scr->geometry().contains(p))
867 return scr;
868 }
869 return nullptr;
870}
871
873{
874 HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
875 if (hMonitor == nullptr)
876 return nullptr;
877 const auto it =
878 std::find_if(m_screens.cbegin(), m_screens.cend(),
879 [hMonitor](const QWindowsScreen *s)
880 {
881 return s->data().hMonitor == hMonitor
882 && (s->data().flags & QWindowsScreenData::VirtualDesktop) != 0;
883 });
884 return it != m_screens.cend() ? *it : nullptr;
885}
886
887QT_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
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)