5.13.2: qcocoascreen patched from QTBUG-80193

This commit is contained in:
kleuter 2019-11-29 08:40:49 +01:00
parent ad520af861
commit 3085d86f9b
2 changed files with 257 additions and 79 deletions

View File

@ -53,9 +53,6 @@ class QCocoaIntegration;
class QCocoaScreen : public QPlatformScreen class QCocoaScreen : public QPlatformScreen
{ {
public: public:
static void initializeScreens();
static void cleanupScreens();
~QCocoaScreen(); ~QCocoaScreen();
// ---------------------------------------------------- // ----------------------------------------------------
@ -68,6 +65,7 @@ public:
qreal devicePixelRatio() const override { return m_devicePixelRatio; } qreal devicePixelRatio() const override { return m_devicePixelRatio; }
QSizeF physicalSize() const override { return m_physicalSize; } QSizeF physicalSize() const override { return m_physicalSize; }
QDpi logicalDpi() const override { return m_logicalDpi; } QDpi logicalDpi() const override { return m_logicalDpi; }
QDpi logicalBaseDpi() const override { return m_logicalDpi; }
qreal refreshRate() const override { return m_refreshRate; } qreal refreshRate() const override { return m_refreshRate; }
QString name() const override { return m_name; } QString name() const override { return m_name; }
QPlatformCursor *cursor() const override { return m_cursor; } QPlatformCursor *cursor() const override { return m_cursor; }
@ -78,7 +76,6 @@ public:
// ---------------------------------------------------- // ----------------------------------------------------
NSScreen *nativeScreen() const; NSScreen *nativeScreen() const;
void updateProperties();
void requestUpdate(); void requestUpdate();
void deliverUpdateRequests(); void deliverUpdateRequests();
@ -87,6 +84,7 @@ public:
static QCocoaScreen *primaryScreen(); static QCocoaScreen *primaryScreen();
static QCocoaScreen *get(NSScreen *nsScreen); static QCocoaScreen *get(NSScreen *nsScreen);
static QCocoaScreen *get(CGDirectDisplayID displayId); static QCocoaScreen *get(CGDirectDisplayID displayId);
static QCocoaScreen *get(CFUUIDRef uuid);
static CGPoint mapToNative(const QPointF &pos, QCocoaScreen *screen = QCocoaScreen::primaryScreen()); static CGPoint mapToNative(const QPointF &pos, QCocoaScreen *screen = QCocoaScreen::primaryScreen());
static CGRect mapToNative(const QRectF &rect, 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()); static QRectF mapFromNative(CGRect rect, QCocoaScreen *screen = QCocoaScreen::primaryScreen());
private: 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); static void add(CGDirectDisplayID displayId);
QCocoaScreen(CGDirectDisplayID displayId);
void update(CGDirectDisplayID displayId);
void remove(); 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_geometry;
QRect m_availableGeometry; QRect m_availableGeometry;
QDpi m_logicalDpi; QDpi m_logicalDpi;
qreal m_refreshRate; qreal m_refreshRate = 0;
int m_depth; int m_depth = 0;
QString m_name; QString m_name;
QImage::Format m_format; QImage::Format m_format;
QSizeF m_physicalSize; QSizeF m_physicalSize;
QCocoaCursor *m_cursor; QCocoaCursor *m_cursor;
qreal m_devicePixelRatio; qreal m_devicePixelRatio = 0;
CVDisplayLinkRef m_displayLink = nullptr; CVDisplayLinkRef m_displayLink = nullptr;
dispatch_source_t m_displayLinkSource = nullptr; dispatch_source_t m_displayLinkSource = nullptr;
QAtomicInt m_pendingUpdates; QAtomicInt m_pendingUpdates;
friend class QCocoaIntegration;
friend class QCocoaWindow;
friend QDebug operator<<(QDebug debug, const QCocoaScreen *screen); friend QDebug operator<<(QDebug debug, const QCocoaScreen *screen);
}; };

View File

@ -54,67 +54,185 @@
QT_BEGIN_NAMESPACE 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() void QCocoaScreen::initializeScreens()
{ {
uint32_t displayCount = 0; updateScreens();
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);
CGDisplayRegisterReconfigurationCallback([](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags, void *userInfo) { CGDisplayRegisterReconfigurationCallback([](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags, void *userInfo) {
if (flags & kCGDisplayBeginConfigurationFlag)
return; // Wait for changes to apply
Q_UNUSED(userInfo); 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) { const bool beforeReconfigure = flags & kCGDisplayBeginConfigurationFlag;
if (!CGDisplayIsActive(displayId)) { qCDebug(lcQpaScreen).verbosity(0).nospace() << "Display " << displayId
qCDebug(lcQpaScreen) << "Not adding inactive display" << displayId; << (beforeReconfigure ? " about to reconfigure" : " was ")
return; // Will be added when activated << QFlags<CoreGraphics::DisplayChange>(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 { } else {
// Detect changes to the primary screen immediately, instead of Q_ASSERT_X(displayReconfigurationsInProgress, "QCococaScreen",
// waiting for a display reconfigure with kCGDisplaySetMainFlag. "Display configuration transactions are expected to be balanced");
// This ensures that any property updates to the other screens
// will be in reference to the correct primary screen. if (!--displayReconfigurationsInProgress) {
QCocoaScreen *mainDisplay = QCocoaScreen::get(CGMainDisplayID()); qCDebug(lcQpaScreen) << "Display reconfigure transaction completed";
if (QGuiApplication::primaryScreen()->handle() != mainDisplay) { // We optimistically update now, in case the NSScreens have changed
mainDisplay->updateProperties(); updateScreensIfNeeded();
qCInfo(lcQpaScreen) << "Primary screen changed to" << mainDisplay;
QWindowSystemInterface::handlePrimaryScreenChanged(mainDisplay);
} }
if (cocoaScreen == mainDisplay)
return; // Already reconfigured
cocoaScreen->updateProperties();
qCInfo(lcQpaScreen) << "Reconfigured" << cocoaScreen;
} }
}, nullptr); }, 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<CGDirectDisplayID> 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<CFUUIDRef> 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<QCocoaScreen*>(screen->handle());
if (!platformScreen->isOnline())
platformScreen->remove();
else if (platformScreen->isMirroring())
platformScreen->remove();
}
} }
void QCocoaScreen::add(CGDirectDisplayID displayId) void QCocoaScreen::add(CGDirectDisplayID displayId)
{ {
const bool isPrimary = CGDisplayIsMain(displayId);
QCocoaScreen *cocoaScreen = new QCocoaScreen(displayId); QCocoaScreen *cocoaScreen = new QCocoaScreen(displayId);
qCInfo(lcQpaScreen) << "Adding" << cocoaScreen; qCInfo(lcQpaScreen) << "Adding" << cocoaScreen
QWindowSystemInterface::handleScreenAdded(cocoaScreen, CGDisplayIsMain(displayId)); << (isPrimary ? "as new primary screen" : "");
QWindowSystemInterface::handleScreenAdded(cocoaScreen, isPrimary);
} }
QCocoaScreen::QCocoaScreen(CGDirectDisplayID displayId) QCocoaScreen::QCocoaScreen(CGDirectDisplayID displayId)
: QPlatformScreen(), m_displayId(displayId) : QPlatformScreen(), m_displayId(displayId)
{ {
updateProperties(); update(m_displayId);
m_cursor = new QCocoaCursor; m_cursor = new QCocoaCursor;
} }
@ -127,8 +245,6 @@ void QCocoaScreen::cleanupScreens()
void QCocoaScreen::remove() void QCocoaScreen::remove()
{ {
m_displayId = 0; // Prevent stale references during removal
// This may result in the application responding to QGuiApplication::screenRemoved // This may result in the application responding to QGuiApplication::screenRemoved
// by moving the window to another screen, either by setGeometry, or by setScreen. // 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 // 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 // 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, // already changed its screen, but that's only true if comparing the Qt screens,
// not when comparing the NSScreens. // not when comparing the NSScreens.
qCInfo(lcQpaScreen) << "Removing " << this;
QWindowSystemInterface::handleScreenRemoved(this); QWindowSystemInterface::handleScreenRemoved(this);
} }
@ -186,9 +303,14 @@ static QString displayName(CGDirectDisplayID displayID)
return QString(); 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 previousGeometry = m_geometry;
const QRect previousAvailableGeometry = m_availableGeometry; const QRect previousAvailableGeometry = m_availableGeometry;
@ -326,8 +448,8 @@ struct DeferredDebugHelper
void QCocoaScreen::deliverUpdateRequests() void QCocoaScreen::deliverUpdateRequests()
{ {
if (!m_displayId) if (!isOnline())
return; // Screen removed return;
QMacAutoReleasePool pool; QMacAutoReleasePool pool;
@ -351,15 +473,6 @@ void QCocoaScreen::deliverUpdateRequests()
// it on the main thread yet, because the processing of the update request is taking // 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. // too long, or because the update request was deferred due to window live resizing.
qDeferredDebug(screenUpdates) << ", " << framesAheadOfDelivery << " frame(s) ahead"; 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"; qDeferredDebug(screenUpdates) << "; signaling dispatch source";
@ -547,15 +660,38 @@ QPixmap QCocoaScreen::grabWindow(WId view, int x, int y, int width, int height)
return windowPixmap; 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 The screen used as a reference for global window geometry
*/ */
QCocoaScreen *QCocoaScreen::primaryScreen() QCocoaScreen *QCocoaScreen::primaryScreen()
{ {
auto screen = static_cast<QCocoaScreen *>(QGuiApplication::primaryScreen()->handle()); // Note: The primary screen that Qt knows about may not match the current CGMainDisplayID()
Q_ASSERT_X(screen == get(CGMainDisplayID()), "QCocoaScreen", // if macOS has not yet been able to inform us that the main display has changed, but we
"The application's primary screen should always be in sync with the main display"); // will update the primary screen accordingly once the reconfiguration callback comes in.
return screen; return static_cast<QCocoaScreen *>(QGuiApplication::primaryScreen()->handle());
} }
QList<QPlatformScreen*> QCocoaScreen::virtualSiblings() const QList<QPlatformScreen*> QCocoaScreen::virtualSiblings() const
@ -571,6 +707,12 @@ QList<QPlatformScreen*> QCocoaScreen::virtualSiblings() const
QCocoaScreen *QCocoaScreen::get(NSScreen *nsScreen) 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); return get(nsScreen.qt_displayId);
} }
@ -585,23 +727,34 @@ QCocoaScreen *QCocoaScreen::get(CGDirectDisplayID displayId)
return nullptr; return nullptr;
} }
QCocoaScreen *QCocoaScreen::get(CFUUIDRef uuid)
{
for (QScreen *screen : QGuiApplication::screens()) {
auto *platformScreen = static_cast<QCocoaScreen*>(screen->handle());
if (!platformScreen->isOnline())
continue;
auto displayId = platformScreen->displayId();
QCFType<CFUUIDRef> candidateUuid(CGDisplayCreateUUIDFromDisplayID(displayId));
Q_ASSERT(candidateUuid);
if (candidateUuid == uuid)
return platformScreen;
}
return nullptr;
}
NSScreen *QCocoaScreen::nativeScreen() const NSScreen *QCocoaScreen::nativeScreen() const
{ {
if (!m_displayId) if (!m_displayId)
return nil; // The display has been disconnected return nil; // The display has been disconnected
// A single display may have different displayIds depending on for (NSScreen *screen in NSScreen.screens) {
// which GPU is in use or which physical port the display is if (screen.qt_displayId == m_displayId)
// 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; return screen;
} }
qCWarning(lcQpaScreen) << "Could not find NSScreen for display ID" << m_displayId;
return nil; return nil;
} }
@ -636,16 +789,29 @@ QDebug operator<<(QDebug debug, const QCocoaScreen *screen)
debug.nospace(); debug.nospace();
debug << "QCocoaScreen(" << (const void *)screen; debug << "QCocoaScreen(" << (const void *)screen;
if (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 << ", dpr=" << screen->devicePixelRatio();
debug << ", name=" << screen->name(); debug << ", displayId=" << screen->displayId();
debug << ", native=" << screen->nativeScreen();
if (auto nativeScreen = screen->nativeScreen())
debug << ", " << nativeScreen;
} }
debug << ')'; debug << ')';
return debug; return debug;
} }
#endif // !QT_NO_DEBUG_STREAM #endif // !QT_NO_DEBUG_STREAM
#include "qcocoascreen.moc"
QT_END_NAMESPACE QT_END_NAMESPACE
@implementation NSScreen (QtExtras) @implementation NSScreen (QtExtras)