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:
parent
591116490c
commit
3976df2805
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -207,9 +207,7 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList)
|
||||
// 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) {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user