From 3085d86f9b59858df4f6c06fb67cd4fb83468b00 Mon Sep 17 00:00:00 2001 From: kleuter Date: Fri, 29 Nov 2019 08:40:49 +0100 Subject: [PATCH] 5.13.2: qcocoascreen patched from QTBUG-80193 --- .../plugins/platforms/cocoa/qcocoascreen.h | 30 +- .../plugins/platforms/cocoa/qcocoascreen.mm | 306 ++++++++++++++---- 2 files changed, 257 insertions(+), 79 deletions(-) diff --git a/5.13.2/qtbase/src/plugins/platforms/cocoa/qcocoascreen.h b/5.13.2/qtbase/src/plugins/platforms/cocoa/qcocoascreen.h index 491af2f..dcf6f1c 100644 --- a/5.13.2/qtbase/src/plugins/platforms/cocoa/qcocoascreen.h +++ b/5.13.2/qtbase/src/plugins/platforms/cocoa/qcocoascreen.h @@ -53,9 +53,6 @@ class QCocoaIntegration; class QCocoaScreen : public QPlatformScreen { public: - static void initializeScreens(); - static void cleanupScreens(); - ~QCocoaScreen(); // ---------------------------------------------------- @@ -68,6 +65,7 @@ public: qreal devicePixelRatio() const override { return m_devicePixelRatio; } QSizeF physicalSize() const override { return m_physicalSize; } QDpi logicalDpi() const override { return m_logicalDpi; } + QDpi logicalBaseDpi() 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; } @@ -78,7 +76,6 @@ public: // ---------------------------------------------------- NSScreen *nativeScreen() const; - void updateProperties(); void requestUpdate(); void deliverUpdateRequests(); @@ -87,6 +84,7 @@ public: static QCocoaScreen *primaryScreen(); static QCocoaScreen *get(NSScreen *nsScreen); static QCocoaScreen *get(CGDirectDisplayID displayId); + static QCocoaScreen *get(CFUUIDRef uuid); static CGPoint mapToNative(const QPointF &pos, QCocoaScreen *screen = QCocoaScreen::primaryScreen()); static CGRect mapToNative(const QRectF &rect, QCocoaScreen *screen = QCocoaScreen::primaryScreen()); @@ -94,27 +92,41 @@ public: static QRectF mapFromNative(CGRect rect, QCocoaScreen *screen = QCocoaScreen::primaryScreen()); private: - QCocoaScreen(CGDirectDisplayID displayId); + static void initializeScreens(); + static void updateScreens(); + static void cleanupScreens(); + + static bool updateScreensIfNeeded(); + static NSArray *s_screenConfigurationBeforeUpdate; + static void add(CGDirectDisplayID displayId); + QCocoaScreen(CGDirectDisplayID displayId); + void update(CGDirectDisplayID displayId); void remove(); - CGDirectDisplayID m_displayId = 0; + bool isOnline() const; + bool isMirroring() const; + + CGDirectDisplayID m_displayId = kCGNullDirectDisplay; + CGDirectDisplayID displayId() const { return m_displayId; } QRect m_geometry; QRect m_availableGeometry; QDpi m_logicalDpi; - qreal m_refreshRate; - int m_depth; + qreal m_refreshRate = 0; + int m_depth = 0; QString m_name; QImage::Format m_format; QSizeF m_physicalSize; QCocoaCursor *m_cursor; - qreal m_devicePixelRatio; + qreal m_devicePixelRatio = 0; CVDisplayLinkRef m_displayLink = nullptr; dispatch_source_t m_displayLinkSource = nullptr; QAtomicInt m_pendingUpdates; + friend class QCocoaIntegration; + friend class QCocoaWindow; friend QDebug operator<<(QDebug debug, const QCocoaScreen *screen); }; diff --git a/5.13.2/qtbase/src/plugins/platforms/cocoa/qcocoascreen.mm b/5.13.2/qtbase/src/plugins/platforms/cocoa/qcocoascreen.mm index 392099d..b24700e 100644 --- a/5.13.2/qtbase/src/plugins/platforms/cocoa/qcocoascreen.mm +++ b/5.13.2/qtbase/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -54,67 +54,185 @@ QT_BEGIN_NAMESPACE +namespace CoreGraphics { + Q_NAMESPACE + enum DisplayChange { + ReconfiguredWithFlagsMissing = 0, + Moved = kCGDisplayMovedFlag, + SetMain = kCGDisplaySetMainFlag, + SetMode = kCGDisplaySetModeFlag, + Added = kCGDisplayAddFlag, + Removed = kCGDisplayRemoveFlag, + Enabled = kCGDisplayEnabledFlag, + Disabled = kCGDisplayDisabledFlag, + Mirrored = kCGDisplayMirrorFlag, + UnMirrored = kCGDisplayUnMirrorFlag, + DesktopShapeChanged = kCGDisplayDesktopShapeChangedFlag + }; + Q_ENUM_NS(DisplayChange) +} + +NSArray *QCocoaScreen::s_screenConfigurationBeforeUpdate = nil; + void QCocoaScreen::initializeScreens() { - uint32_t displayCount = 0; - if (CGGetActiveDisplayList(0, nullptr, &displayCount) != kCGErrorSuccess) - qFatal("Failed to get number of active displays"); - - CGDirectDisplayID activeDisplays[displayCount]; - if (CGGetActiveDisplayList(displayCount, &activeDisplays[0], &displayCount) != kCGErrorSuccess) - qFatal("Failed to get active displays"); - - for (CGDirectDisplayID displayId : activeDisplays) - QCocoaScreen::add(displayId); + updateScreens(); CGDisplayRegisterReconfigurationCallback([](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags, void *userInfo) { - if (flags & kCGDisplayBeginConfigurationFlag) - return; // Wait for changes to apply - Q_UNUSED(userInfo); - QCocoaScreen *cocoaScreen = QCocoaScreen::get(displayId); + // Displays are reconfigured in batches, and we want to update our screens + // once a batch ends, so that all the states of the displays are up to date. + static int displayReconfigurationsInProgress = 0; - if ((flags & kCGDisplayAddFlag) || !cocoaScreen) { - if (!CGDisplayIsActive(displayId)) { - qCDebug(lcQpaScreen) << "Not adding inactive display" << displayId; - return; // Will be added when activated + const bool beforeReconfigure = flags & kCGDisplayBeginConfigurationFlag; + qCDebug(lcQpaScreen).verbosity(0).nospace() << "Display " << displayId + << (beforeReconfigure ? " about to reconfigure" : " was ") + << QFlags(flags) + << " with " << displayReconfigurationsInProgress + << " display configuration(s) in progress"; + + if (!flags) { + // CGDisplayRegisterReconfigurationCallback has been observed to be called + // with flags unset. This seems like a bug. The callback is not paired with + // a matching "completion" callback either, so we don't know whether to treat + // it as a begin or end of reconfigure. + return; + } + + if (beforeReconfigure) { + if (!displayReconfigurationsInProgress++) { + // There might have been a screen reconfigure before this that + // we didn't process yet, so do that now if that's the case. + updateScreensIfNeeded(); + + Q_ASSERT(!s_screenConfigurationBeforeUpdate); + s_screenConfigurationBeforeUpdate = NSScreen.screens; + qCDebug(lcQpaScreen, "Display reconfigure transaction started" + " with screen configuration %p", s_screenConfigurationBeforeUpdate); + + static void (^tryScreenUpdate)(); + tryScreenUpdate = ^void () { + qCDebug(lcQpaScreen) << "Attempting screen update from runloop block"; + if (!updateScreensIfNeeded()) + CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, tryScreenUpdate); + }; + CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, tryScreenUpdate); } - 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); + Q_ASSERT_X(displayReconfigurationsInProgress, "QCococaScreen", + "Display configuration transactions are expected to be balanced"); + + if (!--displayReconfigurationsInProgress) { + qCDebug(lcQpaScreen) << "Display reconfigure transaction completed"; + // We optimistically update now, in case the NSScreens have changed + updateScreensIfNeeded(); } - - if (cocoaScreen == mainDisplay) - return; // Already reconfigured - - cocoaScreen->updateProperties(); - qCInfo(lcQpaScreen) << "Reconfigured" << cocoaScreen; } }, nullptr); + + static QMacNotificationObserver screenParamaterObserver(NSApplication.sharedApplication, + NSApplicationDidChangeScreenParametersNotification, [&]() { + qCDebug(lcQpaScreen) << "Received screen parameter change notification"; + updateScreensIfNeeded(); // As a last resort we update screens here + }); +} + +bool QCocoaScreen::updateScreensIfNeeded() +{ + if (!s_screenConfigurationBeforeUpdate) { + qCDebug(lcQpaScreen) << "QScreens have already been updated, all good"; + return true; + } + + if (s_screenConfigurationBeforeUpdate == NSScreen.screens) { + qCDebug(lcQpaScreen) << "Still waiting for NSScreen configuration change"; + return false; + } + + qCDebug(lcQpaScreen, "NSScreen configuration changed to %p", NSScreen.screens); + updateScreens(); + + s_screenConfigurationBeforeUpdate = nil; + return true; +} + +/* + Update the list of available QScreens, and the properties of existing screens. + + At this point we rely on the NSScreen.screens to be up to date. +*/ +void QCocoaScreen::updateScreens() +{ + uint32_t displayCount = 0; + if (CGGetOnlineDisplayList(0, nullptr, &displayCount) != kCGErrorSuccess) + qFatal("Failed to get number of online displays"); + + QVector onlineDisplays(displayCount); + if (CGGetOnlineDisplayList(displayCount, onlineDisplays.data(), &displayCount) != kCGErrorSuccess) + qFatal("Failed to get online displays"); + + qCInfo(lcQpaScreen) << "Updating screens with" << displayCount + << "online displays:" << onlineDisplays; + + // TODO: Verify whether we can always assume the main display is first + int mainDisplayIndex = onlineDisplays.indexOf(CGMainDisplayID()); + if (mainDisplayIndex < 0) { + qCWarning(lcQpaScreen) << "Main display not in list of online displays!"; + } else if (mainDisplayIndex > 0) { + qCWarning(lcQpaScreen) << "Main display not first display, making sure it is"; + onlineDisplays.move(mainDisplayIndex, 0); + } + + for (CGDirectDisplayID displayId : onlineDisplays) { + Q_ASSERT(CGDisplayIsOnline(displayId)); + + if (CGDirectDisplayID mirroring = CGDisplayMirrorsDisplay(displayId)) + continue; + + // A single physical screen can map to multiple displays IDs, + // depending on which GPU is in use or which physical port the + // screen is connected to. By mapping the display ID to a UUID, + // which are shared between displays that target the same screen, + // we can pick an existing QScreen to update instead of needlessly + // adding and removing QScreens. + QCFType uuid = CGDisplayCreateUUIDFromDisplayID(displayId); + Q_ASSERT(uuid); + + if (QCocoaScreen *existingScreen = QCocoaScreen::get(uuid)) { + existingScreen->update(displayId); + qCInfo(lcQpaScreen) << "Updated" << existingScreen; + if (CGDisplayIsMain(displayId) && existingScreen != qGuiApp->primaryScreen()->handle()) { + qCInfo(lcQpaScreen) << "Primary screen changed to" << existingScreen; + QWindowSystemInterface::handlePrimaryScreenChanged(existingScreen); + } + } else { + QCocoaScreen::add(displayId); + } + } + + for (QScreen *screen : QGuiApplication::screens()) { + QCocoaScreen *platformScreen = static_cast(screen->handle()); + if (!platformScreen->isOnline()) + platformScreen->remove(); + else if (platformScreen->isMirroring()) + platformScreen->remove(); + } } void QCocoaScreen::add(CGDirectDisplayID displayId) { + const bool isPrimary = CGDisplayIsMain(displayId); QCocoaScreen *cocoaScreen = new QCocoaScreen(displayId); - qCInfo(lcQpaScreen) << "Adding" << cocoaScreen; - QWindowSystemInterface::handleScreenAdded(cocoaScreen, CGDisplayIsMain(displayId)); + qCInfo(lcQpaScreen) << "Adding" << cocoaScreen + << (isPrimary ? "as new primary screen" : ""); + QWindowSystemInterface::handleScreenAdded(cocoaScreen, isPrimary); } QCocoaScreen::QCocoaScreen(CGDirectDisplayID displayId) : QPlatformScreen(), m_displayId(displayId) { - updateProperties(); + update(m_displayId); m_cursor = new QCocoaCursor; } @@ -127,8 +245,6 @@ void QCocoaScreen::cleanupScreens() 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 @@ -140,6 +256,7 @@ void QCocoaScreen::remove() // 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. + qCInfo(lcQpaScreen) << "Removing " << this; QWindowSystemInterface::handleScreenRemoved(this); } @@ -186,9 +303,14 @@ static QString displayName(CGDirectDisplayID displayID) return QString(); } -void QCocoaScreen::updateProperties() +void QCocoaScreen::update(CGDirectDisplayID displayId) { - Q_ASSERT(m_displayId); + if (displayId != m_displayId) { + qCDebug(lcQpaScreen) << "Reconnecting" << this << "as display" << displayId; + m_displayId = displayId; + } + + Q_ASSERT(isOnline()); const QRect previousGeometry = m_geometry; const QRect previousAvailableGeometry = m_availableGeometry; @@ -326,8 +448,8 @@ struct DeferredDebugHelper void QCocoaScreen::deliverUpdateRequests() { - if (!m_displayId) - return; // Screen removed + if (!isOnline()) + return; QMacAutoReleasePool pool; @@ -351,15 +473,6 @@ void QCocoaScreen::deliverUpdateRequests() // it on the main thread yet, because the processing of the update request is taking // too long, or because the update request was deferred due to window live resizing. qDeferredDebug(screenUpdates) << ", " << framesAheadOfDelivery << " frame(s) ahead"; - - // We skip the frame completely if we're live-resizing, to not put any extra - // strain on the main thread runloop. Otherwise we assume we should push frames - // as fast as possible, and hopefully the callback will be delivered on the - // main thread just when the previous finished. - if (qt_apple_sharedApplication().keyWindow.inLiveResize) { - qDeferredDebug(screenUpdates) << "; waiting for main thread to catch up"; - return; - } } qDeferredDebug(screenUpdates) << "; signaling dispatch source"; @@ -547,15 +660,38 @@ QPixmap QCocoaScreen::grabWindow(WId view, int x, int y, int width, int height) return windowPixmap; } +bool QCocoaScreen::isOnline() const +{ + // When a display is disconnected CGDisplayIsOnline and other CGDisplay + // functions that take a displayId will not return false, but will start + // returning -1 to signal that the displayId is invalid. Some functions + // will also assert or even crash in this case, so it's important that + // we double check if a display is online before calling other functions. + auto isOnline = CGDisplayIsOnline(m_displayId); + static const uint32_t kCGDisplayIsDisconnected = int32_t(-1); + return isOnline != kCGDisplayIsDisconnected && isOnline; +} + +/* + Returns true if a screen is mirroring another screen +*/ +bool QCocoaScreen::isMirroring() const +{ + if (!isOnline()) + return false; + + return CGDisplayMirrorsDisplay(m_displayId); +} + /*! The screen used as a reference for global window geometry */ QCocoaScreen *QCocoaScreen::primaryScreen() { - auto screen = static_cast(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; + // Note: The primary screen that Qt knows about may not match the current CGMainDisplayID() + // if macOS has not yet been able to inform us that the main display has changed, but we + // will update the primary screen accordingly once the reconfiguration callback comes in. + return static_cast(QGuiApplication::primaryScreen()->handle()); } QList QCocoaScreen::virtualSiblings() const @@ -571,6 +707,12 @@ QList QCocoaScreen::virtualSiblings() const QCocoaScreen *QCocoaScreen::get(NSScreen *nsScreen) { + if (s_screenConfigurationBeforeUpdate) { + qCWarning(lcQpaScreen) << "Trying to resolve screen while waiting for screen reconfigure!"; + if (!updateScreensIfNeeded()) + qCWarning(lcQpaScreen) << "Failed to do last minute screen update. Expect crashes."; + } + return get(nsScreen.qt_displayId); } @@ -585,23 +727,34 @@ QCocoaScreen *QCocoaScreen::get(CGDirectDisplayID displayId) return nullptr; } +QCocoaScreen *QCocoaScreen::get(CFUUIDRef uuid) +{ + for (QScreen *screen : QGuiApplication::screens()) { + auto *platformScreen = static_cast(screen->handle()); + if (!platformScreen->isOnline()) + continue; + + auto displayId = platformScreen->displayId(); + QCFType candidateUuid(CGDisplayCreateUUIDFromDisplayID(displayId)); + Q_ASSERT(candidateUuid); + + if (candidateUuid == uuid) + return platformScreen; + } + + 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 uuid = CGDisplayCreateUUIDFromDisplayID(m_displayId); - - for (NSScreen *screen in [NSScreen screens]) { - if (CGDisplayCreateUUIDFromDisplayID(screen.qt_displayId) == uuid) + for (NSScreen *screen in NSScreen.screens) { + if (screen.qt_displayId == m_displayId) return screen; } - qCWarning(lcQpaScreen) << "Could not find NSScreen for display ID" << m_displayId; return nil; } @@ -636,16 +789,29 @@ QDebug operator<<(QDebug debug, const QCocoaScreen *screen) debug.nospace(); debug << "QCocoaScreen(" << (const void *)screen; if (screen) { - debug << ", geometry=" << screen->geometry(); + debug << ", " << screen->name(); + if (screen->isOnline()) { + if (CGDisplayIsAsleep(screen->displayId())) + debug << ", Sleeping"; + if (auto mirroring = CGDisplayMirrorsDisplay(screen->displayId())) + debug << ", mirroring=" << mirroring; + } else { + debug << ", Offline"; + } + debug << ", " << screen->geometry(); debug << ", dpr=" << screen->devicePixelRatio(); - debug << ", name=" << screen->name(); - debug << ", native=" << screen->nativeScreen(); + debug << ", displayId=" << screen->displayId(); + + if (auto nativeScreen = screen->nativeScreen()) + debug << ", " << nativeScreen; } debug << ')'; return debug; } #endif // !QT_NO_DEBUG_STREAM +#include "qcocoascreen.moc" + QT_END_NAMESPACE @implementation NSScreen (QtExtras)