macOS: Track screens via Quartz Display Services instead of NSScreen

Using NSScreen as the basis for tracking screens is not recommended, as
the list of screens can be added, removed, or dynamically reconfigured at
any time, and the NSScreen instance, or index in the NSScreen.screens array
may not be stable.

Quartz Display Services on the other hand tracks displays via a unique
display ID, which typically remains constant until the machine is restarted.
The lower level API also gives us earlier callbacks about screen changes
than the corresponding NSApplicationDidChangeScreenParametersNotification
does. By reacting to screen changes _before_ AppKit does, we can remove
workarounds for receiving window move and screen change notifications
before the screen was actually visibly reconfigured.

The new approach also handles changes to the primary screen, which
can happen if the user moves the menu bar in the macOS display
arrangement pane.

The device pixel ratio of the screen has been made into a cached
property, like all the other properties of QCocoaScreen. This is
more consistent, and allows us to qDebug the screen even when it
has been removed and we no longer have access to resolve the
properties from the associated Quarts display.

Change-Id: I2d86c7629ed3bf5fb8c77f174712633752ae4079
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Tor Arne Vestbø 2019-03-21 15:38:41 +01:00
parent 591116490c
commit 3976df2805
9 changed files with 206 additions and 187 deletions

View File

@ -273,18 +273,6 @@ void QNSWindowBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const
// ----------------------------------------------------------------------------
// https://stackoverflow.com/a/52722575/2761869
template<class R>
struct backwards_t {
R r;
constexpr auto begin() const { using std::rbegin; return rbegin(r); }
constexpr auto begin() { using std::rbegin; return rbegin(r); }
constexpr auto end() const { using std::rend; return rend(r); }
constexpr auto end() { using std::rend; return rend(r); }
};
template<class R>
constexpr backwards_t<R> backwards(R&& r) { return {std::forward<R>(r)}; }
QCALayerBackingStore::QCALayerBackingStore(QWindow *window)
: QPlatformBackingStore(window)
{

View File

@ -176,6 +176,18 @@ T qt_mac_resolveOption(const T &fallback, QWindow *window, const QByteArray &pro
return fallback;
}
// https://stackoverflow.com/a/52722575/2761869
template<class R>
struct backwards_t {
R r;
constexpr auto begin() const { using std::rbegin; return rbegin(r); }
constexpr auto begin() { using std::rbegin; return rbegin(r); }
constexpr auto end() const { using std::rend; return rend(r); }
constexpr auto end() { using std::rend; return rend(r); }
};
template<class R>
constexpr backwards_t<R> backwards(R&& r) { return {std::forward<R>(r)}; }
// -------------------------------------------------------------------------
#if !defined(Q_PROCESSOR_X86_64)

View File

@ -63,7 +63,7 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window");
Q_LOGGING_CATEGORY(lcQpaDrawing, "qt.qpa.drawing");
Q_LOGGING_CATEGORY(lcQpaMouse, "qt.qpa.input.mouse", QtCriticalMsg);
Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen");
Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen", QtCriticalMsg);
//
// Conversion Functions

View File

@ -61,8 +61,6 @@
QT_BEGIN_NAMESPACE
class QCocoaScreen;
class QCocoaIntegration : public QObject, public QPlatformIntegration
{
Q_OBJECT
@ -113,9 +111,6 @@ public:
Qt::KeyboardModifiers queryKeyboardModifiers() const override;
QList<int> possibleKeys(const QKeyEvent *event) const override;
void updateScreens();
QCocoaScreen *screenForNSScreen(NSScreen *nsScreen);
void setToolbar(QWindow *window, NSToolbar *toolbar);
NSToolbar *toolbar(QWindow *window) const;
void clearToolbars();
@ -143,8 +138,6 @@ private:
QScopedPointer<QCocoaAccessibility> mAccessibility;
#endif
QScopedPointer<QPlatformTheme> mPlatformTheme;
QList<QCocoaScreen *> mScreens;
QMacScopedObserver m_screensObserver;
#ifndef QT_NO_CLIPBOARD
QCocoaClipboard *mCocoaClipboard;
#endif

View File

@ -207,9 +207,7 @@ QCocoaIntegration::QCocoaIntegration(const QStringList &paramList)
// which will resolve to an actual value and result in screen invalidation.
cocoaApplication.presentationOptions = NSApplicationPresentationDefault;
m_screensObserver = QMacScopedObserver([NSApplication sharedApplication],
NSApplicationDidChangeScreenParametersNotification, [&]() { updateScreens(); });
updateScreens();
QCocoaScreen::initializeScreens();
QMacInternalPasteboardMime::initializeMimeTypes();
QCocoaMimeTypes::initializeMimeTypes();
@ -242,10 +240,7 @@ QCocoaIntegration::~QCocoaIntegration()
QMacInternalPasteboardMime::destroyMimeTypes();
#endif
// Delete screens in reverse order to avoid crash in case of multiple screens
while (!mScreens.isEmpty()) {
QWindowSystemInterface::handleScreenRemoved(mScreens.takeLast());
}
QCocoaScreen::cleanupScreens();
clearToolbars();
}
@ -260,88 +255,6 @@ QCocoaIntegration::Options QCocoaIntegration::options() const
return mOptions;
}
/*!
\brief Synchronizes the screen list, adds new screens, removes deleted ones
*/
void QCocoaIntegration::updateScreens()
{
NSArray<NSScreen *> *scrs = [NSScreen screens];
NSMutableArray<NSScreen *> *screens = [NSMutableArray<NSScreen *> arrayWithArray:scrs];
if ([screens count] == 0)
if ([NSScreen mainScreen])
[screens addObject:[NSScreen mainScreen]];
if ([screens count] == 0)
return;
QSet<QCocoaScreen*> remainingScreens = QSet<QCocoaScreen*>::fromList(mScreens);
QList<QPlatformScreen *> siblings;
uint screenCount = [screens count];
for (uint i = 0; i < screenCount; i++) {
NSScreen* scr = [screens objectAtIndex:i];
CGDirectDisplayID dpy = scr.qt_displayId;
// If this screen is a mirror and is not the primary one of the mirror set, ignore it.
// Exception: The NSScreen API has been observed to a return a screen list with one
// mirrored, non-primary screen when Qt is running as a startup item. Always use the
// screen if there's only one screen in the list.
if (screenCount > 1 && CGDisplayIsInMirrorSet(dpy)) {
CGDirectDisplayID primary = CGDisplayMirrorsDisplay(dpy);
if (primary != kCGNullDirectDisplay && primary != dpy)
continue;
}
QCocoaScreen* screen = nullptr;
foreach (QCocoaScreen* existingScr, mScreens) {
// NSScreen documentation says do not cache the array returned from [NSScreen screens].
// However in practice, we can identify a screen by its pointer: if resolution changes,
// the NSScreen object will be the same instance, just with different values.
if (existingScr->nativeScreen() == scr) {
screen = existingScr;
break;
}
}
if (screen) {
remainingScreens.remove(screen);
screen->updateProperties();
} else {
screen = new QCocoaScreen(i);
mScreens.append(screen);
qCDebug(lcQpaScreen) << "Adding" << screen;
QWindowSystemInterface::handleScreenAdded(screen);
}
siblings << screen;
}
// Set virtual siblings list. All screens in mScreens are siblings, because we ignored the
// mirrors. Note that some of the screens we update the siblings list for here may be deleted
// below, but update anyway to keep the to-be-deleted screens out of the siblings list.
foreach (QCocoaScreen* screen, mScreens)
screen->setVirtualSiblings(siblings);
// Now the leftovers in remainingScreens are no longer current, so we can delete them.
foreach (QCocoaScreen* screen, remainingScreens) {
mScreens.removeOne(screen);
// Prevent stale references to NSScreen during destroy
screen->m_screenIndex = -1;
qCDebug(lcQpaScreen) << "Removing" << screen;
QWindowSystemInterface::handleScreenRemoved(screen);
}
}
QCocoaScreen *QCocoaIntegration::screenForNSScreen(NSScreen *nsScreen)
{
NSUInteger index = [[NSScreen screens] indexOfObject:nsScreen];
if (index == NSNotFound)
return nullptr;
if (index >= unsigned(mScreens.count()))
updateScreens();
for (QCocoaScreen *screen : mScreens) {
if (screen->nativeScreen() == nsScreen)
return screen;
}
return nullptr;
}
bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) const
{
switch (cap) {

View File

@ -48,10 +48,14 @@
QT_BEGIN_NAMESPACE
class QCocoaIntegration;
class QCocoaScreen : public QPlatformScreen
{
public:
QCocoaScreen(int screenIndex);
static void initializeScreens();
static void cleanupScreens();
~QCocoaScreen();
// ----------------------------------------------------
@ -61,19 +65,18 @@ public:
QRect availableGeometry() const override { return m_availableGeometry; }
int depth() const override { return m_depth; }
QImage::Format format() const override { return m_format; }
qreal devicePixelRatio() const override;
qreal devicePixelRatio() const override { return m_devicePixelRatio; }
QSizeF physicalSize() const override { return m_physicalSize; }
QDpi logicalDpi() const override { return m_logicalDpi; }
qreal refreshRate() const override { return m_refreshRate; }
QString name() const override { return m_name; }
QPlatformCursor *cursor() const override { return m_cursor; }
QWindow *topLevelAt(const QPoint &point) const override;
QList<QPlatformScreen *> virtualSiblings() const override { return m_siblings; }
QList<QPlatformScreen *> virtualSiblings() const override;
QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const override;
// ----------------------------------------------------
// Additional methods
void setVirtualSiblings(const QList<QPlatformScreen *> &siblings) { m_siblings = siblings; }
NSScreen *nativeScreen() const;
void updateProperties();
@ -82,14 +85,21 @@ public:
bool isRunningDisplayLink() const;
static QCocoaScreen *primaryScreen();
static QCocoaScreen *get(NSScreen *nsScreen);
static QCocoaScreen *get(CGDirectDisplayID displayId);
static CGPoint mapToNative(const QPointF &pos, QCocoaScreen *screen = QCocoaScreen::primaryScreen());
static CGRect mapToNative(const QRectF &rect, QCocoaScreen *screen = QCocoaScreen::primaryScreen());
static QPointF mapFromNative(CGPoint pos, QCocoaScreen *screen = QCocoaScreen::primaryScreen());
static QRectF mapFromNative(CGRect rect, QCocoaScreen *screen = QCocoaScreen::primaryScreen());
public:
int m_screenIndex;
private:
QCocoaScreen(CGDirectDisplayID displayId);
static void add(CGDirectDisplayID displayId);
void remove();
CGDirectDisplayID m_displayId = 0;
QRect m_geometry;
QRect m_availableGeometry;
QDpi m_logicalDpi;
@ -99,11 +109,13 @@ public:
QImage::Format m_format;
QSizeF m_physicalSize;
QCocoaCursor *m_cursor;
QList<QPlatformScreen *> m_siblings;
qreal m_devicePixelRatio;
CVDisplayLinkRef m_displayLink = nullptr;
dispatch_source_t m_displayLinkSource = nullptr;
QAtomicInt m_pendingUpdates;
friend QDebug operator<<(QDebug debug, const QCocoaScreen *screen);
};
#ifndef QT_NO_DEBUG_STREAM
@ -116,5 +128,4 @@ QT_END_NAMESPACE
@property(readonly) CGDirectDisplayID qt_displayId;
@end
#endif
#endif // QCOCOASCREEN_H

View File

@ -41,6 +41,7 @@
#include "qcocoawindow.h"
#include "qcocoahelpers.h"
#include "qcocoaintegration.h"
#include <QtCore/qcoreapplication.h>
#include <QtGui/private/qcoregraphics_p.h>
@ -53,18 +54,99 @@
QT_BEGIN_NAMESPACE
class QCoreTextFontEngine;
class QFontEngineFT;
void QCocoaScreen::initializeScreens()
{
uint32_t displayCount = 0;
if (CGGetActiveDisplayList(0, nullptr, &displayCount) != kCGErrorSuccess)
qFatal("Failed to get number of active displays");
QCocoaScreen::QCocoaScreen(int screenIndex)
: QPlatformScreen(), m_screenIndex(screenIndex), m_refreshRate(60.0)
CGDirectDisplayID activeDisplays[displayCount];
if (CGGetActiveDisplayList(displayCount, &activeDisplays[0], &displayCount) != kCGErrorSuccess)
qFatal("Failed to get active displays");
for (CGDirectDisplayID displayId : activeDisplays)
QCocoaScreen::add(displayId);
CGDisplayRegisterReconfigurationCallback([](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags, void *userInfo) {
if (flags & kCGDisplayBeginConfigurationFlag)
return; // Wait for changes to apply
Q_UNUSED(userInfo);
QCocoaScreen *cocoaScreen = QCocoaScreen::get(displayId);
if ((flags & kCGDisplayAddFlag) || !cocoaScreen) {
if (!CGDisplayIsActive(displayId)) {
qCDebug(lcQpaScreen) << "Not adding inactive display" << displayId;
return; // Will be added when activated
}
QCocoaScreen::add(displayId);
} else if ((flags & kCGDisplayRemoveFlag) || !CGDisplayIsActive(displayId)) {
cocoaScreen->remove();
} else {
// Detect changes to the primary screen immediately, instead of
// waiting for a display reconfigure with kCGDisplaySetMainFlag.
// This ensures that any property updates to the other screens
// will be in reference to the correct primary screen.
QCocoaScreen *mainDisplay = QCocoaScreen::get(CGMainDisplayID());
if (QGuiApplication::primaryScreen()->handle() != mainDisplay) {
mainDisplay->updateProperties();
qCInfo(lcQpaScreen) << "Primary screen changed to" << mainDisplay;
QWindowSystemInterface::handlePrimaryScreenChanged(mainDisplay);
}
if (cocoaScreen == mainDisplay)
return; // Already reconfigured
cocoaScreen->updateProperties();
qCInfo(lcQpaScreen) << "Reconfigured" << cocoaScreen;
}
}, nullptr);
}
void QCocoaScreen::add(CGDirectDisplayID displayId)
{
QCocoaScreen *cocoaScreen = new QCocoaScreen(displayId);
qCInfo(lcQpaScreen) << "Adding" << cocoaScreen;
QWindowSystemInterface::handleScreenAdded(cocoaScreen, CGDisplayIsMain(displayId));
}
QCocoaScreen::QCocoaScreen(CGDirectDisplayID displayId)
: QPlatformScreen(), m_displayId(displayId)
{
updateProperties();
m_cursor = new QCocoaCursor;
}
void QCocoaScreen::cleanupScreens()
{
// Remove screens in reverse order to avoid crash in case of multiple screens
for (QScreen *screen : backwards(QGuiApplication::screens()))
static_cast<QCocoaScreen*>(screen->handle())->remove();
}
void QCocoaScreen::remove()
{
m_displayId = 0; // Prevent stale references during removal
// This may result in the application responding to QGuiApplication::screenRemoved
// by moving the window to another screen, either by setGeometry, or by setScreen.
// If the window isn't moved by the application, Qt will as a fallback move it to
// the primary screen via setScreen. Due to the way setScreen works, this won't
// actually recreate the window on the new screen, it will just assign the new
// QScreen to the window. The associated NSWindow will have an NSScreen determined
// by AppKit. AppKit will then move the window to another screen by changing the
// geometry, and we will get a callback in QCocoaWindow::windowDidMove and then
// QCocoaWindow::windowDidChangeScreen. At that point the window will appear to have
// already changed its screen, but that's only true if comparing the Qt screens,
// not when comparing the NSScreens.
QWindowSystemInterface::handleScreenRemoved(this);
}
QCocoaScreen::~QCocoaScreen()
{
Q_ASSERT_X(!screen(), "QCocoaScreen", "QScreen should be deleted first");
delete m_cursor;
CVDisplayLinkRelease(m_displayLink);
@ -72,17 +154,6 @@ QCocoaScreen::~QCocoaScreen()
dispatch_release(m_displayLinkSource);
}
NSScreen *QCocoaScreen::nativeScreen() const
{
NSArray<NSScreen *> *screens = [NSScreen screens];
// Stale reference, screen configuration has changed
if (m_screenIndex < 0 || (NSUInteger)m_screenIndex >= [screens count])
return nil;
return [screens objectAtIndex:m_screenIndex];
}
static QString displayName(CGDirectDisplayID displayID)
{
QIOType<io_iterator_t> iterator;
@ -117,35 +188,37 @@ static QString displayName(CGDirectDisplayID displayID)
void QCocoaScreen::updateProperties()
{
NSScreen *nsScreen = nativeScreen();
if (!nsScreen)
return;
Q_ASSERT(m_displayId);
const QRect previousGeometry = m_geometry;
const QRect previousAvailableGeometry = m_availableGeometry;
const QDpi previousLogicalDpi = m_logicalDpi;
const qreal previousRefreshRate = m_refreshRate;
// Some properties are only available via NSScreen
NSScreen *nsScreen = nativeScreen();
Q_ASSERT(nsScreen);
// The reference screen for the geometry is always the primary screen
QRectF primaryScreenGeometry = QRectF::fromCGRect([[NSScreen screens] firstObject].frame);
QRectF primaryScreenGeometry = QRectF::fromCGRect(CGDisplayBounds(CGMainDisplayID()));
m_geometry = qt_mac_flip(QRectF::fromCGRect(nsScreen.frame), primaryScreenGeometry).toRect();
m_availableGeometry = qt_mac_flip(QRectF::fromCGRect(nsScreen.visibleFrame), primaryScreenGeometry).toRect();
m_format = QImage::Format_RGB32;
m_depth = NSBitsPerPixelFromDepth([nsScreen depth]);
m_devicePixelRatio = nsScreen.backingScaleFactor;
CGDirectDisplayID dpy = nsScreen.qt_displayId;
CGSize size = CGDisplayScreenSize(dpy);
m_format = QImage::Format_RGB32;
m_depth = NSBitsPerPixelFromDepth(nsScreen.depth);
CGSize size = CGDisplayScreenSize(m_displayId);
m_physicalSize = QSizeF(size.width, size.height);
m_logicalDpi.first = 72;
m_logicalDpi.second = 72;
CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode(dpy);
float refresh = CGDisplayModeGetRefreshRate(displayMode);
CGDisplayModeRelease(displayMode);
if (refresh > 0)
m_refreshRate = refresh;
m_name = displayName(dpy);
QCFType<CGDisplayModeRef> displayMode = CGDisplayCopyDisplayMode(m_displayId);
float refresh = CGDisplayModeGetRefreshRate(displayMode);
m_refreshRate = refresh > 0 ? refresh : 60.0;
m_name = displayName(m_displayId);
const bool didChangeGeometry = m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry;
@ -155,24 +228,6 @@ void QCocoaScreen::updateProperties()
QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second);
if (m_refreshRate != previousRefreshRate)
QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate);
qCDebug(lcQpaScreen) << "Updated properties for" << this;
if (didChangeGeometry) {
// When a screen changes its geometry, AppKit will send us a NSWindowDidMoveNotification
// for each window, resulting in calls to handleGeometryChange(), but this happens before
// the NSApplicationDidChangeScreenParametersNotification, so when we map the new geometry
// (which is correct at that point) to the screen using QCocoaScreen::mapFromNative(), we
// end up using the stale screen geometry, and the new window geometry we report is wrong.
// To make sure we finally report the correct window geometry, we need to do another pass
// of geometry reporting, now that the screen properties have been updates. FIXME: Ideally
// this would be solved by not caching the screen properties in QCocoaScreen, but that
// requires more research.
for (QWindow *window : windows()) {
if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow*>(window->handle()))
cocoaWindow->handleGeometryChange();
}
}
}
// ----------------------- Display link -----------------------
@ -181,8 +236,10 @@ Q_LOGGING_CATEGORY(lcQpaScreenUpdates, "qt.qpa.screen.updates", QtCriticalMsg);
void QCocoaScreen::requestUpdate()
{
Q_ASSERT(m_displayId);
if (!m_displayLink) {
CVDisplayLinkCreateWithCGDisplay(nativeScreen().qt_displayId, &m_displayLink);
CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink);
CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef, const CVTimeStamp*,
const CVTimeStamp*, CVOptionFlags, CVOptionFlags*, void* displayLinkContext) -> int {
// FIXME: It would be nice if update requests would include timing info
@ -269,6 +326,9 @@ struct DeferredDebugHelper
void QCocoaScreen::deliverUpdateRequests()
{
if (!m_displayId)
return; // Screen removed
QMacAutoReleasePool pool;
// The CVDisplayLink callback is a notification that it's a good time to produce a new frame.
@ -283,7 +343,7 @@ void QCocoaScreen::deliverUpdateRequests()
const int pendingUpdates = ++m_pendingUpdates;
DeferredDebugHelper screenUpdates(lcQpaScreenUpdates());
qDeferredDebug(screenUpdates) << "display link callback for screen " << m_screenIndex;
qDeferredDebug(screenUpdates) << "display link callback for screen " << m_displayId;
if (const int framesAheadOfDelivery = pendingUpdates - 1) {
// If we have more than one update pending it means that a previous display link callback
@ -370,13 +430,6 @@ bool QCocoaScreen::isRunningDisplayLink() const
// -----------------------------------------------------------
qreal QCocoaScreen::devicePixelRatio() const
{
QMacAutoReleasePool pool;
NSScreen *nsScreen = nativeScreen();
return qreal(nsScreen ? [nsScreen backingScaleFactor] : 1.0);
}
QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingTypeHint() const
{
QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint();
@ -430,7 +483,7 @@ QPixmap QCocoaScreen::grabWindow(WId view, int x, int y, int width, int height)
{
// Determine the grab rect. FIXME: The rect should be bounded by the view's
// geometry, but note that for the pixeltool use case that window will be the
// desktop widgets's view, which currently gets resized to fit one screen
// desktop widget's view, which currently gets resized to fit one screen
// only, since its NSWindow has the NSWindowStyleMaskTitled flag set.
Q_UNUSED(view);
QRect grabRect = QRect(x, y, width, height);
@ -482,7 +535,7 @@ QPixmap QCocoaScreen::grabWindow(WId view, int x, int y, int width, int height)
for (uint i = 0; i < displayCount; ++i)
dpr = qMax(dpr, images.at(i).devicePixelRatio());
// Alocate target pixmap and draw each screen's content
// Allocate target pixmap and draw each screen's content
qCDebug(lcQpaScreen) << "Create grap pixmap" << grabRect.size() << "at devicePixelRatio" << dpr;
QPixmap windowPixmap(grabRect.size() * dpr);
windowPixmap.setDevicePixelRatio(dpr);
@ -499,7 +552,57 @@ QPixmap QCocoaScreen::grabWindow(WId view, int x, int y, int width, int height)
*/
QCocoaScreen *QCocoaScreen::primaryScreen()
{
return static_cast<QCocoaScreen *>(QGuiApplication::primaryScreen()->handle());
auto screen = static_cast<QCocoaScreen *>(QGuiApplication::primaryScreen()->handle());
Q_ASSERT_X(screen == get(CGMainDisplayID()), "QCocoaScreen",
"The application's primary screen should always be in sync with the main display");
return screen;
}
QList<QPlatformScreen*> QCocoaScreen::virtualSiblings() const
{
QList<QPlatformScreen*> siblings;
// Screens on macOS are always part of the same virtual desktop
for (QScreen *screen : QGuiApplication::screens())
siblings << screen->handle();
return siblings;
}
QCocoaScreen *QCocoaScreen::get(NSScreen *nsScreen)
{
return get(nsScreen.qt_displayId);
}
QCocoaScreen *QCocoaScreen::get(CGDirectDisplayID displayId)
{
for (QScreen *screen : QGuiApplication::screens()) {
QCocoaScreen *cocoaScreen = static_cast<QCocoaScreen*>(screen->handle());
if (cocoaScreen->m_displayId == displayId)
return cocoaScreen;
}
return nullptr;
}
NSScreen *QCocoaScreen::nativeScreen() const
{
if (!m_displayId)
return nil; // The display has been disconnected
// A single display may have different displayIds depending on
// which GPU is in use or which physical port the display is
// connected to. By comparing UUIDs instead of display IDs we
// ensure that we always pick up the appropriate NSScreen.
QCFType<CFUUIDRef> uuid = CGDisplayCreateUUIDFromDisplayID(m_displayId);
for (NSScreen *screen in [NSScreen screens]) {
if (CGDisplayCreateUUIDFromDisplayID(screen.qt_displayId) == uuid)
return screen;
}
qCWarning(lcQpaScreen) << "Could not find NSScreen for display ID" << m_displayId;
return nil;
}
CGPoint QCocoaScreen::mapToNative(const QPointF &pos, QCocoaScreen *screen)
@ -533,11 +636,10 @@ QDebug operator<<(QDebug debug, const QCocoaScreen *screen)
debug.nospace();
debug << "QCocoaScreen(" << (const void *)screen;
if (screen) {
debug << ", index=" << screen->m_screenIndex;
debug << ", native=" << screen->nativeScreen();
debug << ", geometry=" << screen->geometry();
debug << ", dpr=" << screen->devicePixelRatio();
debug << ", name=" << screen->name();
debug << ", native=" << screen->nativeScreen();
}
debug << ')';
return debug;

View File

@ -383,9 +383,9 @@ QT_END_NAMESPACE
}
- (QRectF)geometry {
if (NSWindow *window = [[item view] window]) {
if (QCocoaScreen *screen = QCocoaIntegration::instance()->screenForNSScreen([window screen]))
return screen->mapFromNative([window frame]);
if (NSWindow *window = item.view.window) {
if (QCocoaScreen *screen = QCocoaScreen::get(window.screen))
return screen->mapFromNative(window.frame);
}
return QRectF();
}

View File

@ -1209,17 +1209,17 @@ void QCocoaWindow::windowDidChangeScreen()
return;
// Note: When a window is resized to 0x0 Cocoa will report the window's screen as nil
auto *currentScreen = QCocoaIntegration::instance()->screenForNSScreen(m_view.window.screen);
auto *currentScreen = QCocoaScreen::get(m_view.window.screen);
auto *previousScreen = static_cast<QCocoaScreen*>(screen());
Q_ASSERT_X(!m_view.window.screen || currentScreen,
"QCocoaWindow", "Failed to get QCocoaScreen for NSScreen");
// Note: The previous screen may be the same as the current screen, either because
// the screen was just reconfigured, which still results in AppKit sending an
// NSWindowDidChangeScreenNotification, because the previous screen was removed,
// a) the screen was just reconfigured, which still results in AppKit sending an
// NSWindowDidChangeScreenNotification, b) because the previous screen was removed,
// and we ended up calling QWindow::setScreen to move the window, which doesn't
// actually move the window to the new screen, or because we've delivered the
// actually move the window to the new screen, or c) because we've delivered the
// screen change to the top level window, which will make all the child windows
// of that window report the new screen when requested via QWindow::screen().
// We still need to deliver the screen change in all these cases, as the