summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qcocoascreen.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoascreen.mm')
-rw-r--r--src/plugins/platforms/cocoa/qcocoascreen.mm301
1 files changed, 140 insertions, 161 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm
index d326d149f1..be562e5455 100644
--- a/src/plugins/platforms/cocoa/qcocoascreen.mm
+++ b/src/plugins/platforms/cocoa/qcocoascreen.mm
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the plugins of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <AppKit/AppKit.h>
@@ -51,7 +15,9 @@
#include <IOKit/graphics/IOGraphicsLib.h>
#include <QtGui/private/qwindow_p.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <QtCore/private/qeventdispatcher_cf_p.h>
QT_BEGIN_NAMESPACE
@@ -74,7 +40,6 @@ namespace CoreGraphics {
Q_ENUM_NS(DisplayChange)
}
-NSArray *QCocoaScreen::s_screenConfigurationBeforeUpdate = nil;
QMacNotificationObserver QCocoaScreen::s_screenParameterObserver;
CGDisplayReconfigurationCallBack QCocoaScreen::s_displayReconfigurationCallBack = nullptr;
@@ -85,83 +50,23 @@ void QCocoaScreen::initializeScreens()
s_displayReconfigurationCallBack = [](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags, void *userInfo) {
Q_UNUSED(userInfo);
- // 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;
-
const bool beforeReconfigure = flags & kCGDisplayBeginConfigurationFlag;
- qCDebug(lcQpaScreen).verbosity(0).nospace() << "Display " << displayId
- << (beforeReconfigure ? " about to reconfigure" : " was ")
- << 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;
- }
+ qCDebug(lcQpaScreen).verbosity(0) << "Display" << displayId
+ << (beforeReconfigure ? "beginning" : "finished") << "reconfigure"
+ << QFlags<CoreGraphics::DisplayChange>(flags);
- 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);
- }
- } else {
- 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 (!beforeReconfigure)
+ updateScreens();
};
CGDisplayRegisterReconfigurationCallback(s_displayReconfigurationCallBack, nullptr);
s_screenParameterObserver = QMacNotificationObserver(NSApplication.sharedApplication,
NSApplicationDidChangeScreenParametersNotification, [&]() {
qCDebug(lcQpaScreen) << "Received screen parameter change notification";
- updateScreensIfNeeded(); // As a last resort we update screens here
+ updateScreens();
});
}
-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.
@@ -169,6 +74,18 @@ bool QCocoaScreen::updateScreensIfNeeded()
*/
void QCocoaScreen::updateScreens()
{
+ // Adding, updating, or removing a screen below might trigger
+ // Qt or the application to move a window to a different screen,
+ // recursing back here via QCocoaWindow::windowDidChangeScreen.
+ // The update code is not re-entrant, so bail out if we end up
+ // in this situation. The screens will stabilize eventually.
+ static bool updatingScreens = false;
+ if (updatingScreens) {
+ qCInfo(lcQpaScreen) << "Skipping screen update, already updating";
+ return;
+ }
+ QBoolBlocker recursionGuard(updatingScreens);
+
uint32_t displayCount = 0;
if (CGGetOnlineDisplayList(0, nullptr, &displayCount) != kCGErrorSuccess)
qFatal("Failed to get number of online displays");
@@ -283,7 +200,7 @@ QCocoaScreen::~QCocoaScreen()
static QString displayName(CGDirectDisplayID displayID)
{
QIOType<io_iterator_t> iterator;
- if (IOServiceGetMatchingServices(kIOMasterPortDefault,
+ if (IOServiceGetMatchingServices(kIOMainPortDefault,
IOServiceMatching("IODisplayConnect"), &iterator))
return QString();
@@ -293,13 +210,13 @@ static QString displayName(CGDirectDisplayID displayID)
NSDictionary *info = [(__bridge NSDictionary*)IODisplayCreateInfoDictionary(
display, kIODisplayOnlyPreferredName) autorelease];
- if ([[info objectForKey:@kDisplayVendorID] longValue] != CGDisplayVendorNumber(displayID))
+ if ([[info objectForKey:@kDisplayVendorID] unsignedIntValue] != CGDisplayVendorNumber(displayID))
continue;
- if ([[info objectForKey:@kDisplayProductID] longValue] != CGDisplayModelNumber(displayID))
+ if ([[info objectForKey:@kDisplayProductID] unsignedIntValue] != CGDisplayModelNumber(displayID))
continue;
- if ([[info objectForKey:@kDisplaySerialNumber] longValue] != CGDisplaySerialNumber(displayID))
+ if ([[info objectForKey:@kDisplaySerialNumber] unsignedIntValue] != CGDisplaySerialNumber(displayID))
continue;
NSDictionary *localizedNames = [info objectForKey:@kDisplayProductName];
@@ -321,14 +238,17 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
Q_ASSERT(isOnline());
+ // Some properties are only available via NSScreen
+ NSScreen *nsScreen = nativeScreen();
+ if (!nsScreen) {
+ qCDebug(lcQpaScreen) << "Corresponding NSScreen not yet available. Deferring update";
+ return;
+ }
+
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);
+ const double previousRotation = m_rotation;
// The reference screen for the geometry is always the primary screen
QRectF primaryScreenGeometry = QRectF::fromCGRect(CGDisplayBounds(CGMainDisplayID()));
@@ -341,27 +261,32 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
m_depth = NSBitsPerPixelFromDepth(nsScreen.depth);
m_colorSpace = QColorSpace::fromIccProfile(QByteArray::fromNSData(nsScreen.colorSpace.ICCProfileData));
if (!m_colorSpace.isValid()) {
- qWarning() << "macOS generated a color-profile Qt couldn't parse. This shouldn't happen.";
+ qCWarning(lcQpaScreen) << "Failed to parse ICC profile for" << nsScreen.colorSpace
+ << "with ICC data" << nsScreen.colorSpace.ICCProfileData
+ << "- Falling back to sRGB";
m_colorSpace = QColorSpace::SRgb;
}
CGSize size = CGDisplayScreenSize(m_displayId);
m_physicalSize = QSizeF(size.width, size.height);
- m_logicalDpi.first = 72;
- m_logicalDpi.second = 72;
QCFType<CGDisplayModeRef> displayMode = CGDisplayCopyDisplayMode(m_displayId);
float refresh = CGDisplayModeGetRefreshRate(displayMode);
m_refreshRate = refresh > 0 ? refresh : 60.0;
+ m_rotation = CGDisplayRotation(displayId);
- m_name = displayName(m_displayId);
+ if (@available(macOS 10.15, *))
+ m_name = QString::fromNSString(nsScreen.localizedName);
+ else
+ m_name = displayName(m_displayId);
const bool didChangeGeometry = m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry;
+ if (m_rotation != previousRotation)
+ QWindowSystemInterface::handleScreenOrientationChange(screen(), orientation());
+
if (didChangeGeometry)
QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry());
- if (m_logicalDpi != previousLogicalDpi)
- QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second);
if (m_refreshRate != previousRefreshRate)
QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate);
}
@@ -370,19 +295,33 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
Q_LOGGING_CATEGORY(lcQpaScreenUpdates, "qt.qpa.screen.updates", QtCriticalMsg);
-void QCocoaScreen::requestUpdate()
+bool QCocoaScreen::requestUpdate()
{
Q_ASSERT(m_displayId);
+ if (!isOnline()) {
+ qCDebug(lcQpaScreenUpdates) << this << "is not online. Ignoring update request";
+ return false;
+ }
+
if (!m_displayLink) {
- CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink);
+ qCDebug(lcQpaScreenUpdates) << "Creating display link for" << this;
+ if (CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink) != kCVReturnSuccess) {
+ qCWarning(lcQpaScreenUpdates) << "Failed to create display link for" << this;
+ return false;
+ }
+ if (auto displayId = CVDisplayLinkGetCurrentCGDisplay(m_displayLink); displayId != m_displayId) {
+ qCWarning(lcQpaScreenUpdates) << "Unexpected display" << displayId << "for display link";
+ CVDisplayLinkRelease(m_displayLink);
+ m_displayLink = nullptr;
+ return false;
+ }
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
static_cast<QCocoaScreen*>(displayLinkContext)->deliverUpdateRequests();
return kCVReturnSuccess;
}, this);
- qCDebug(lcQpaScreenUpdates) << "Display link created for" << this;
// During live window resizing -[NSWindow _resizeWithEvent:] will spin a local event loop
// in event-tracking mode, dequeuing only the mouse drag events needed to update the window's
@@ -437,6 +376,8 @@ void QCocoaScreen::requestUpdate()
qCDebug(lcQpaScreenUpdates) << "Starting display link for" << this;
CVDisplayLinkStart(m_displayLink);
}
+
+ return true;
}
// Helper to allow building up debug output in multiple steps
@@ -530,6 +471,25 @@ void QCocoaScreen::deliverUpdateRequests()
if (!platformWindow->updatesWithDisplayLink())
continue;
+ // QTBUG-107198: Skip updates in a live resize for a better resize experience.
+ if (platformWindow->isContentView() && platformWindow->view().inLiveResize) {
+ const QSurface::SurfaceType surfaceType = window->surfaceType();
+ const bool usesMetalLayer = surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface;
+ const bool usesNonDefaultContentsPlacement = [platformWindow->view() layerContentsPlacement]
+ != NSViewLayerContentsPlacementScaleAxesIndependently;
+ if (usesMetalLayer && usesNonDefaultContentsPlacement) {
+ static bool deliverDisplayLinkUpdatesDuringLiveResize =
+ qEnvironmentVariableIsSet("QT_MAC_DISPLAY_LINK_UPDATE_IN_RESIZE");
+ if (!deliverDisplayLinkUpdatesDuringLiveResize) {
+ // Must keep the link running, we do not know what the event
+ // handlers for UpdateRequest (which is not sent now) would do,
+ // would they trigger a new requestUpdate() or not.
+ pauseUpdates = false;
+ continue;
+ }
+ }
+ }
+
platformWindow->deliverUpdateRequest();
// Another update request was triggered, keep the display link running
@@ -567,41 +527,56 @@ QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingType
return type;
}
+Qt::ScreenOrientation QCocoaScreen::orientation() const
+{
+ if (m_rotation == 0)
+ return Qt::LandscapeOrientation;
+ if (m_rotation == 90)
+ return Qt::PortraitOrientation;
+ if (m_rotation == 180)
+ return Qt::InvertedLandscapeOrientation;
+ if (m_rotation == 270)
+ return Qt::InvertedPortraitOrientation;
+ return QPlatformScreen::orientation();
+}
+
QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const
{
- NSPoint screenPoint = mapToNative(point);
-
- // Search (hit test) for the top-level window. [NSWidow windowNumberAtPoint:
- // belowWindowWithWindowNumber] may return windows that are not interesting
- // to Qt. The search iterates until a suitable window or no window is found.
- NSInteger topWindowNumber = 0;
- QWindow *window = nullptr;
- do {
- // Get the top-most window, below any previously rejected window.
- topWindowNumber = [NSWindow windowNumberAtPoint:screenPoint
- belowWindowWithWindowNumber:topWindowNumber];
-
- // Continue the search if the window does not belong to this process.
- NSWindow *nsWindow = [NSApp windowWithWindowNumber:topWindowNumber];
- if (!nsWindow)
- continue;
+ __block QWindow *window = nullptr;
+ [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
+ usingBlock:^(NSWindow *nsWindow, BOOL *stop) {
+ if (!nsWindow)
+ return;
- // Continue the search if the window does not belong to Qt.
- if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
- continue;
+ // Continue the search if the window does not belong to Qt
+ if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
+ return;
- QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
- if (!cocoaWindow)
- continue;
- window = cocoaWindow->window();
+ QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
+ if (!cocoaWindow)
+ return;
+
+ QWindow *w = cocoaWindow->window();
+ if (!w->isVisible())
+ return;
+
+ auto nativeGeometry = QHighDpi::toNativePixels(w->geometry(), w);
+ if (!nativeGeometry.contains(point))
+ return;
+
+ QRegion mask = QHighDpi::toNativeLocalPosition(w->mask(), w);
+ if (!mask.isEmpty() && !mask.contains(point - nativeGeometry.topLeft()))
+ return;
- // Continue the search if the window is not a top-level window.
- if (!window->isTopLevel())
- continue;
+ window = w;
- // Stop searching. The current window is the correct window.
- break;
- } while (topWindowNumber > 0);
+ // Continue the search if the window is not a top-level window
+ if (!window->isTopLevel())
+ return;
+
+ *stop = true;
+ }
+ ];
return window;
}
@@ -610,7 +585,7 @@ QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const
\internal
Coordinates are in screen coordinates if \a view is 0, otherwise they are in view
- coordiantes.
+ coordinates.
*/
QPixmap QCocoaScreen::grabWindow(WId view, int x, int y, int width, int height) const
{
@@ -759,13 +734,17 @@ QList<QPlatformScreen*> 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.";
+ auto displayId = nsScreen.qt_displayId;
+ auto *cocoaScreen = get(displayId);
+ if (!cocoaScreen) {
+ qCWarning(lcQpaScreen) << "Failed to map" << nsScreen
+ << "to QCocoaScreen. Doing last minute update.";
+ updateScreens();
+ cocoaScreen = get(displayId);
+ if (!cocoaScreen)
+ qCWarning(lcQpaScreen) << "Last minute update failed!";
}
-
- return get(nsScreen.qt_displayId);
+ return cocoaScreen;
}
QCocoaScreen *QCocoaScreen::get(CGDirectDisplayID displayId)
@@ -866,10 +845,10 @@ QDebug operator<<(QDebug debug, const QCocoaScreen *screen)
}
#endif // !QT_NO_DEBUG_STREAM
-#include "qcocoascreen.moc"
-
QT_END_NAMESPACE
+#include "qcocoascreen.moc"
+
@implementation NSScreen (QtExtras)
- (CGDirectDisplayID)qt_displayId