diff options
Diffstat (limited to 'src/plugins/platforms/cocoa')
39 files changed, 1627 insertions, 742 deletions
diff --git a/src/plugins/platforms/cocoa/CMakeLists.txt b/src/plugins/platforms/cocoa/CMakeLists.txt index 2f1ae9a565..0b08d49390 100644 --- a/src/plugins/platforms/cocoa/CMakeLists.txt +++ b/src/plugins/platforms/cocoa/CMakeLists.txt @@ -1,3 +1,5 @@ +# Generated from cocoa.pro. + # special case: find_package(Cups) find_package(WrapOpenGL) @@ -38,6 +40,7 @@ add_qt_plugin(qcocoa qcocoasystemtrayicon.h qcocoasystemtrayicon.mm qcocoatheme.h qcocoatheme.mm qcocoawindow.h qcocoawindow.mm + qiosurfacegraphicsbuffer.h qiosurfacegraphicsbuffer.mm qmacclipboard.h qmacclipboard.mm qmultitouch_mac.mm qmultitouch_mac_p.h qnsview.h qnsview.mm @@ -57,6 +60,7 @@ add_qt_plugin(qcocoa ${FWCoreServices} ${FWCoreVideo} ${FWIOKit} + ${FWIOSurface} ${FWMetal} ${FWQuartzCore} Cups::Cups @@ -76,7 +80,7 @@ add_qt_resource(qcocoa "qcocoaresources" PREFIX "/qt-project.org/mac/cursors" FI images/waitcursor.png) -#### Keys ignored in scope 1:.:.:./cocoa.pro:<TRUE>: +#### Keys ignored in scope 1:.:.:cocoa.pro:<TRUE>: # CONFIG = "no_app_extension_api_only" # OTHER_FILES = "cocoa.json" # PLUGIN_CLASS_NAME = "QCocoaIntegrationPlugin" @@ -89,7 +93,7 @@ extend_target(qcocoa CONDITION QT_FEATURE_opengl # special case SOURCES qcocoaglcontext.h qcocoaglcontext.mm LIBRARIES - WrapOpenGL + WrapOpenGL # special case ) extend_target(qcocoa CONDITION QT_FEATURE_vulkan @@ -115,7 +119,7 @@ extend_target(qcocoa CONDITION TARGET Qt::Widgets Qt::Widgets ) -#### Keys ignored in scope 5:.:.:./cocoa.pro:TARGET Qt::Widgets: +#### Keys ignored in scope 5:.:.:cocoa.pro:TARGET Qt::Widgets: # QT_FOR_CONFIG = "widgets" extend_target(qcocoa CONDITION (TARGET Qt::Widgets) AND (QT_FEATURE_colordialog) @@ -133,5 +137,5 @@ extend_target(qcocoa CONDITION (TARGET Qt::Widgets) AND (QT_FEATURE_fontdialog) qcocoafontdialoghelper.h qcocoafontdialoghelper.mm ) -#### Keys ignored in scope 9:.:.:./cocoa.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN: +#### Keys ignored in scope 9:.:.:cocoa.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN: # PLUGIN_EXTENDS = "-" diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index 8d65cf328f..083b7c1655 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -33,6 +33,7 @@ SOURCES += main.mm \ qcocoaintrospection.mm \ qcocoakeymapper.mm \ qcocoamimetypes.mm \ + qiosurfacegraphicsbuffer.mm \ messages.cpp HEADERS += qcocoaintegration.h \ @@ -67,6 +68,7 @@ HEADERS += qcocoaintegration.h \ qcocoaintrospection.h \ qcocoakeymapper.h \ messages.h \ + qiosurfacegraphicsbuffer.h \ qcocoamimetypes.h qtConfig(opengl.*) { @@ -81,7 +83,7 @@ qtConfig(vulkan) { RESOURCES += qcocoaresources.qrc -LIBS += -framework AppKit -framework CoreServices -framework Carbon -framework IOKit -framework QuartzCore -framework CoreVideo -framework Metal -lcups +LIBS += -framework AppKit -framework CoreServices -framework Carbon -framework IOKit -framework QuartzCore -framework CoreVideo -framework Metal -framework IOSurface -lcups QT += \ core-private gui-private \ diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm index 03dc895ffb..f0ef70e3a3 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm @@ -51,6 +51,19 @@ QT_USE_NAMESPACE #ifndef QT_NO_ACCESSIBILITY +/** + * Converts between absolute character offsets and line numbers of a + * QAccessibleTextInterface. Works in exactly one of two modes: + * + * - Pass *line == -1 in order to get a line containing character at the given + * *offset + * - Pass *offset == -1 in order to get the offset of first character of the + * given *line + * + * You can optionally also pass non-NULL `start` and `end`, which will in both + * modes be filled with the offset of the first and last characters of the + * relevant line. + */ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *offset, NSUInteger *start = 0, NSUInteger *end = 0) { Q_ASSERT(*line == -1 || *offset == -1); @@ -100,7 +113,6 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } @implementation QMacAccessibilityElement { - NSString *role; QAccessible::Id axid; } @@ -110,9 +122,6 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of self = [super init]; if (self) { axid = anId; - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - Q_ASSERT(iface); - role = QCocoaAccessible::macRole(iface); } return self; @@ -228,7 +237,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return [attributes autorelease]; } -- (id)parentElement { +- (id)accessibilityParent { QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); if (!iface || !iface->isValid()) return nil; @@ -241,7 +250,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of if (QAccessibleInterface *parent = iface->parent()) { if (parent->role() != QAccessible::Application) { QAccessible::Id parentId = QAccessible::uniqueId(parent); - return [QMacAccessibilityElement elementWithId: parentId]; + return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId: parentId]); } } @@ -249,12 +258,18 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of QPlatformWindow *platformWindow = window->handle(); if (platformWindow) { QCocoaWindow *win = static_cast<QCocoaWindow*>(platformWindow); - return qnsview_cast(win->view()); + return NSAccessibilityUnignoredAncestor(qnsview_cast(win->view())); } } return nil; } +- (NSRect)accessibilityFrame { + QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); + if (!iface || !iface->isValid()) + return NSZeroRect; + return QCocoaScreen::mapToNative(iface->rect()); +} - (id) minValueAttribute:(QAccessibleInterface*)iface { if (QAccessibleValueInterface *val = iface->valueInterface()) @@ -276,11 +291,12 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { - return role; + return QCocoaAccessible::macRole(iface); } else if ([attribute isEqualToString:NSAccessibilitySubroleAttribute]) { return QCocoaAccessible::macSubrole(iface); } else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { - return NSAccessibilityRoleDescription(role, [self accessibilityAttributeValue:NSAccessibilitySubroleAttribute]); + return NSAccessibilityRoleDescription(QCocoaAccessible::macRole(iface), + [self accessibilityAttributeValue:NSAccessibilitySubroleAttribute]); } else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { return QCocoaAccessible::unignoredChildren(iface); } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { @@ -288,13 +304,13 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of id focusedElement = [NSApp accessibilityAttributeValue:NSAccessibilityFocusedUIElementAttribute]; return @([focusedElement isEqual:self]); } else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) { - return NSAccessibilityUnignoredAncestor([self parentElement]); + return self.accessibilityParent; } else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) { // We're in the same window as our parent. - return [[self parentElement] accessibilityAttributeValue:NSAccessibilityWindowAttribute]; + return [[self accessibilityParent] accessibilityAttributeValue:NSAccessibilityWindowAttribute]; } else if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) { // We're in the same top level element as our parent. - return [[self parentElement] accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute]; + return [[self accessibilityParent] accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute]; } else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) { // The position in points of the element's lower-left corner in screen-relative coordinates QPointF qtPosition = QRectF(iface->rect()).bottomLeft(); @@ -345,16 +361,13 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return [NSValue valueWithRange: NSMakeRange(0, 0)]; } else if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) { // FIXME This is not correct and may impact performance for big texts - return [NSValue valueWithRange: NSMakeRange(0, iface->textInterface()->characterCount())]; - + if (QAccessibleTextInterface *text = iface->textInterface()) + return [NSValue valueWithRange: NSMakeRange(0, text->characterCount())]; + return [NSValue valueWithRange: NSMakeRange(0, iface->text(QAccessible::Name).length())]; } else if ([attribute isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute]) { if (QAccessibleTextInterface *text = iface->textInterface()) { - int line = 0; // true for all single line edits - if (iface->state().multiLine) { - int position = text->cursorPosition(); - convertLineOffset(text, &line, &position); - } - return @(line); + int position = text->cursorPosition(); + return [self accessibilityAttributeValue:NSAccessibilityLineForIndexParameterizedAttribute forParameter:@(position)]; } return nil; } else if ([attribute isEqualToString:NSAccessibilityMinValueAttribute]) { @@ -410,8 +423,11 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of int index = [parameter intValue]; if (index < 0 || index > iface->textInterface()->characterCount()) return nil; - int line = -1; - convertLineOffset(iface->textInterface(), &line, &index); + int line = 0; // true for all single line edits + if (iface->state().multiLine) { + line = -1; + convertLineOffset(iface->textInterface(), &line, &index); + } return @(line); } if ([attribute isEqualToString: NSAccessibilityRangeForLineParameterizedAttribute]) { diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 221a8b0866..2cf6672da9 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -86,6 +86,7 @@ #include <private/qguiapplication_p.h> #include "qt_mac_p.h" #include <qpa/qwindowsysteminterface.h> +#include <qwindowdefs.h> QT_USE_NAMESPACE @@ -114,22 +115,10 @@ QT_USE_NAMESPACE self = [super init]; if (self) { inLaunch = true; - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(updateScreens:) - name:NSApplicationDidChangeScreenParametersNotification - object:NSApp]; } return self; } -- (void)updateScreens:(NSNotification *)notification -{ - Q_UNUSED(notification); - if (QCocoaIntegration *ci = QCocoaIntegration::instance()) - ci->updateScreens(); -} - - (void)dealloc { [_dockMenu release]; @@ -309,25 +298,6 @@ QT_USE_NAMESPACE return NO; // Someday qApp->quitOnLastWindowClosed(); when QApp and NSApp work closer together. } -- (void)applicationWillHide:(NSNotification *)notification -{ - if (reflectionDelegate - && [reflectionDelegate respondsToSelector:@selector(applicationWillHide:)]) { - [reflectionDelegate applicationWillHide:notification]; - } - - // When the application is hidden Qt will hide the popup windows associated with - // it when it has lost the activation for the application. However, when it gets - // to this point it believes the popup windows to be hidden already due to the - // fact that the application itself is hidden, which will cause a problem when - // the application is made visible again. - const QWindowList topLevelWindows = QGuiApplication::topLevelWindows(); - for (QWindow *topLevelWindow : qAsConst(topLevelWindows)) { - if ((topLevelWindow->type() & Qt::Popup) == Qt::Popup && topLevelWindow->isVisible()) - topLevelWindow->hide(); - } -} - - (void)applicationDidBecomeActive:(NSNotification *)notification { if (reflectionDelegate @@ -335,21 +305,6 @@ QT_USE_NAMESPACE [reflectionDelegate applicationDidBecomeActive:notification]; QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive); -/* - onApplicationChangedActivation(true); - - if (!QWidget::mouseGrabber()){ - // Update enter/leave immidiatly, don't wait for a move event. But only - // if no grab exists (even if the grab points to this widget, it seems, ref X11) - QPoint qlocal, qglobal; - QWidget *widgetUnderMouse = 0; - qt_mac_getTargetForMouseEvent(0, QEvent::Enter, qlocal, qglobal, 0, &widgetUnderMouse); - QApplicationPrivate::dispatchEnterLeave(widgetUnderMouse, 0); - qt_last_mouse_receiver = widgetUnderMouse; - qt_last_native_mouse_receiver = widgetUnderMouse ? - (widgetUnderMouse->internalWinId() ? widgetUnderMouse : widgetUnderMouse->nativeParentWidget()) : 0; - } -*/ } - (void)applicationDidResignActive:(NSNotification *)notification @@ -359,15 +314,6 @@ QT_USE_NAMESPACE [reflectionDelegate applicationDidResignActive:notification]; QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationInactive); -/* - onApplicationChangedActivation(false); - - if (!QWidget::mouseGrabber()) - QApplicationPrivate::dispatchEnterLeave(0, qt_last_mouse_receiver); - qt_last_mouse_receiver = 0; - qt_last_native_mouse_receiver = 0; - qt_button_down = 0; -*/ } - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index b4cd506513..508f24d578 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -44,13 +44,16 @@ #include <private/qcore_mac_p.h> +#include <QScopedPointer> +#include "qiosurfacegraphicsbuffer.h" + QT_BEGIN_NAMESPACE -class QCocoaBackingStore : public QRasterBackingStore +class QNSWindowBackingStore : public QRasterBackingStore { public: - QCocoaBackingStore(QWindow *window); - ~QCocoaBackingStore(); + QNSWindowBackingStore(QWindow *window); + ~QNSWindowBackingStore(); void flush(QWindow *, const QRegion &, const QPoint &) override; @@ -60,6 +63,49 @@ private: void redrawRoundedBottomCorners(CGRect) const; }; +class QCALayerBackingStore : public QPlatformBackingStore +{ +public: + QCALayerBackingStore(QWindow *window); + ~QCALayerBackingStore(); + + void resize(const QSize &size, const QRegion &staticContents) override; + + void beginPaint(const QRegion ®ion) override; + QPaintDevice *paintDevice() override; + void endPaint() override; + + void flush(QWindow *, const QRegion &, const QPoint &) override; + void composeAndFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, + QPlatformTextureList *textures, bool translucentBackground) override; + + QPlatformGraphicsBuffer *graphicsBuffer() const override; + +private: + QSize m_requestedSize; + QRegion m_paintedRegion; + + class GraphicsBuffer : public QIOSurfaceGraphicsBuffer + { + public: + GraphicsBuffer(const QSize &size, qreal devicePixelRatio, + const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace); + + QRegion dirtyRegion; // In unscaled coordinates + QImage *asImage(); + + private: + qreal m_devicePixelRatio; + QImage m_image; + }; + + void ensureBackBuffer(); + bool recreateBackBufferIfNeeded(); + bool prepareForFlush(); + + std::list<std::unique_ptr<GraphicsBuffer>> m_buffers; +}; + QT_END_NAMESPACE #endif diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 81a0a7d040..8e4e928bc5 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -42,24 +42,28 @@ #include "qcocoawindow.h" #include "qcocoahelpers.h" +#include <QtCore/qmath.h> + +#include <QuartzCore/CATransaction.h> + QT_BEGIN_NAMESPACE -QCocoaBackingStore::QCocoaBackingStore(QWindow *window) +QNSWindowBackingStore::QNSWindowBackingStore(QWindow *window) : QRasterBackingStore(window) { } -QCocoaBackingStore::~QCocoaBackingStore() +QNSWindowBackingStore::~QNSWindowBackingStore() { } -bool QCocoaBackingStore::windowHasUnifiedToolbar() const +bool QNSWindowBackingStore::windowHasUnifiedToolbar() const { Q_ASSERT(window()->handle()); return static_cast<QCocoaWindow *>(window()->handle())->m_drawContentBorderGradient; } -QImage::Format QCocoaBackingStore::format() const +QImage::Format QNSWindowBackingStore::format() const { if (windowHasUnifiedToolbar()) return QImage::Format_ARGB32_Premultiplied; @@ -78,7 +82,7 @@ QImage::Format QCocoaBackingStore::format() const coordinates, and the \a offset will be the child window's offset in relation to the backingstore's top level window. */ -void QCocoaBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) +void QNSWindowBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) { if (m_image.isNull()) return; @@ -103,131 +107,113 @@ void QCocoaBackingStore::flush(QWindow *window, const QRegion ®ion, const QPo qCDebug(lcQpaBackingStore) << "Flushing" << region << "of" << view << qPrintable(targetViewDescription); } - // Prevent potentially costly color conversion by assigning the display color space - // to the backingstore image. This does not copy the underlying image data. - CGColorSpaceRef displayColorSpace = view.window.screen.colorSpace.CGColorSpace; - QCFType<CGImageRef> cgImage = CGImageCreateCopyWithColorSpace( - QCFType<CGImageRef>(m_image.toCGImage()), displayColorSpace); - - if (view.layer) { - // In layer-backed mode, locking focus on a view does not give the right - // view transformation, and doesn't give us a graphics context to render - // via when drawing outside of the display cycle. Instead we tell AppKit - // that we want to update the layer's content, via [NSView wantsUpdateLayer], - // which result in AppKit not creating a backingstore for each layer, and - // we then directly set the layer's backingstore (content) to our backingstore, - // masked to the part of the subview that is relevant. - // FIXME: Figure out if there's a way to do partial updates - view.layer.contents = (__bridge id)static_cast<CGImageRef>(cgImage); - if (view != topLevelView) { - const CGSize topLevelSize = topLevelView.bounds.size; - view.layer.contentsRect = CGRectApplyAffineTransform( - [view convertRect:view.bounds toView:topLevelView], - // The contentsRect is in unit coordinate system - CGAffineTransformMakeScale(1.0 / topLevelSize.width, 1.0 / topLevelSize.height)); - } - } else { - // Normally a NSView is drawn via drawRect, as part of the display cycle in the - // main runloop, via setNeedsDisplay and friends. AppKit will lock focus on each - // individual view, starting with the top level and then traversing any subviews, - // calling drawRect for each of them. This pull model results in expose events - // sent to Qt, which result in drawing to the backingstore and flushing it. - // Qt may also decide to paint and flush the backingstore via e.g. timers, - // or other events such as mouse events, in which case we're in a push model. - // If there is no focused view, it means we're in the latter case, and need - // to manually flush the NSWindow after drawing to its graphic context. - const bool drawingOutsideOfDisplayCycle = ![NSView focusView]; - - // We also need to ensure the flushed view has focus, so that the graphics - // context is set up correctly (coordinate system, clipping, etc). Outside - // of the normal display cycle there is no focused view, as explained above, - // so we have to handle it manually. There's also a corner case inside the - // normal display cycle due to way QWidgetBackingStore composits native child - // widgets, where we'll get a flush of a native child during the drawRect of - // its parent/ancestor, and the parent/ancestor being the one locked by AppKit. - // In this case we also need to lock and unlock focus manually. - const bool shouldHandleViewLockManually = [NSView focusView] != view; - if (shouldHandleViewLockManually && ![view lockFocusIfCanDraw]) { - qWarning() << "failed to lock focus of" << view; - return; - } + // Normally a NSView is drawn via drawRect, as part of the display cycle in the + // main runloop, via setNeedsDisplay and friends. AppKit will lock focus on each + // individual view, starting with the top level and then traversing any subviews, + // calling drawRect for each of them. This pull model results in expose events + // sent to Qt, which result in drawing to the backingstore and flushing it. + // Qt may also decide to paint and flush the backingstore via e.g. timers, + // or other events such as mouse events, in which case we're in a push model. + // If there is no focused view, it means we're in the latter case, and need + // to manually flush the NSWindow after drawing to its graphic context. + const bool drawingOutsideOfDisplayCycle = ![NSView focusView]; + + // We also need to ensure the flushed view has focus, so that the graphics + // context is set up correctly (coordinate system, clipping, etc). Outside + // of the normal display cycle there is no focused view, as explained above, + // so we have to handle it manually. There's also a corner case inside the + // normal display cycle due to way QWidgetBackingStore composits native child + // widgets, where we'll get a flush of a native child during the drawRect of + // its parent/ancestor, and the parent/ancestor being the one locked by AppKit. + // In this case we also need to lock and unlock focus manually. + const bool shouldHandleViewLockManually = [NSView focusView] != view; + if (shouldHandleViewLockManually && ![view lockFocusIfCanDraw]) { + qWarning() << "failed to lock focus of" << view; + return; + } - const qreal devicePixelRatio = m_image.devicePixelRatio(); + const qreal devicePixelRatio = m_image.devicePixelRatio(); - // If the flushed window is a content view, and we're filling the drawn area - // completely, or it doesn't have a window background we need to preserve, - // we can get away with copying instead of blending the backing store. - QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); - const NSCompositingOperation compositingOperation = cocoaWindow->isContentView() - && (cocoaWindow->isOpaque() || view.window.backgroundColor == NSColor.clearColor) - ? NSCompositingOperationCopy : NSCompositingOperationSourceOver; + // If the flushed window is a content view, and we're filling the drawn area + // completely, or it doesn't have a window background we need to preserve, + // we can get away with copying instead of blending the backing store. + QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); + const NSCompositingOperation compositingOperation = cocoaWindow->isContentView() + && (cocoaWindow->isOpaque() || view.window.backgroundColor == NSColor.clearColor) + ? NSCompositingOperationCopy : NSCompositingOperationSourceOver; #ifdef QT_DEBUG - static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults] - boolForKey:@"QtCocoaDebugBackingStoreFlush"]; + static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults] + boolForKey:@"QtCocoaDebugBackingStoreFlush"]; #endif - // ------------------------------------------------------------------------- + // ------------------------------------------------------------------------- - // The current contexts is typically a NSWindowGraphicsContext, but can be - // NSBitmapGraphicsContext e.g. when debugging the view hierarchy in Xcode. - // If we need to distinguish things here in the future, we can use e.g. - // [NSGraphicsContext drawingToScreen], or the attributes of the context. - NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext]; - Q_ASSERT_X(graphicsContext, "QCocoaBackingStore", - "Focusing the view should give us a current graphics context"); + // The current contexts is typically a NSWindowGraphicsContext, but can be + // NSBitmapGraphicsContext e.g. when debugging the view hierarchy in Xcode. + // If we need to distinguish things here in the future, we can use e.g. + // [NSGraphicsContext drawingToScreen], or the attributes of the context. + NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext]; + Q_ASSERT_X(graphicsContext, "QCocoaBackingStore", + "Focusing the view should give us a current graphics context"); - // Create temporary image to use for blitting, without copying image data - NSImage *backingStoreImage = [[[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize] autorelease]; + // Prevent potentially costly color conversion by assigning the display color space + // to the backingstore image. This does not copy the underlying image data. + CGColorSpaceRef displayColorSpace = view.window.screen.colorSpace.CGColorSpace; + QCFType<CGImageRef> cgImage = CGImageCreateCopyWithColorSpace( + QCFType<CGImageRef>(m_image.toCGImage()), displayColorSpace); - QRegion clippedRegion = region; - for (QWindow *w = window; w; w = w->parent()) { - if (!w->mask().isEmpty()) { - clippedRegion &= w == window ? w->mask() - : w->mask().translated(window->mapFromGlobal(w->mapToGlobal(QPoint(0, 0)))); - } + // Create temporary image to use for blitting, without copying image data + NSImage *backingStoreImage = [[[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize] autorelease]; + + QRegion clippedRegion = region; + for (QWindow *w = window; w; w = w->parent()) { + if (!w->mask().isEmpty()) { + clippedRegion &= w == window ? w->mask() + : w->mask().translated(window->mapFromGlobal(w->mapToGlobal(QPoint(0, 0)))); } + } - for (const QRect &viewLocalRect : clippedRegion) { - QPoint backingStoreOffset = viewLocalRect.topLeft() + offset; - QRect backingStoreRect(backingStoreOffset * devicePixelRatio, viewLocalRect.size() * devicePixelRatio); - if (graphicsContext.flipped) // Flip backingStoreRect to match graphics context - backingStoreRect.moveTop(m_image.height() - (backingStoreRect.y() + backingStoreRect.height())); + for (const QRect &viewLocalRect : clippedRegion) { + QPoint backingStoreOffset = viewLocalRect.topLeft() + offset; + QRect backingStoreRect(backingStoreOffset * devicePixelRatio, viewLocalRect.size() * devicePixelRatio); + if (graphicsContext.flipped) // Flip backingStoreRect to match graphics context + backingStoreRect.moveTop(m_image.height() - (backingStoreRect.y() + backingStoreRect.height())); - CGRect viewRect = viewLocalRect.toCGRect(); + CGRect viewRect = viewLocalRect.toCGRect(); - if (windowHasUnifiedToolbar()) - NSDrawWindowBackground(viewRect); + if (windowHasUnifiedToolbar()) + NSDrawWindowBackground(viewRect); - [backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect() - operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil]; + [backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect() + operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil]; #ifdef QT_DEBUG - if (Q_UNLIKELY(debugBackingStoreFlush)) { - [[NSColor colorWithCalibratedRed:drand48() green:drand48() blue:drand48() alpha:0.3] set]; - [NSBezierPath fillRect:viewRect]; - - if (drawingOutsideOfDisplayCycle) { - [[[NSColor magentaColor] colorWithAlphaComponent:0.5] set]; - [NSBezierPath strokeLineFromPoint:viewLocalRect.topLeft().toCGPoint() - toPoint:viewLocalRect.bottomRight().toCGPoint()]; - } + if (Q_UNLIKELY(debugBackingStoreFlush)) { + [[NSColor colorWithCalibratedRed:drand48() green:drand48() blue:drand48() alpha:0.3] set]; + [NSBezierPath fillRect:viewRect]; + + if (drawingOutsideOfDisplayCycle) { + [[[NSColor magentaColor] colorWithAlphaComponent:0.5] set]; + [NSBezierPath strokeLineFromPoint:viewLocalRect.topLeft().toCGPoint() + toPoint:viewLocalRect.bottomRight().toCGPoint()]; } -#endif } +#endif + } - // ------------------------------------------------------------------------- + // ------------------------------------------------------------------------- - if (shouldHandleViewLockManually) - [view unlockFocus]; + if (shouldHandleViewLockManually) + [view unlockFocus]; - if (drawingOutsideOfDisplayCycle) { - redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]); - [view.window flushWindow]; - } + if (drawingOutsideOfDisplayCycle) { + redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]); + [view.window flushWindow]; } - // Done flushing to either CALayer or NSWindow backingstore + + // Done flushing to NSWindow backingstore QCocoaWindow *topLevelCocoaWindow = static_cast<QCocoaWindow *>(topLevelWindow->handle()); if (Q_UNLIKELY(topLevelCocoaWindow->m_needsInvalidateShadow)) { @@ -251,7 +237,7 @@ void QCocoaBackingStore::flush(QWindow *window, const QRegion ®ion, const QPo https://trac.webkit.org/changeset/85376/webkit */ -void QCocoaBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const +void QNSWindowBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const { #if !defined(QT_APPLE_NO_PRIVATE_APIS) Q_ASSERT(this->window()->handle()); @@ -285,4 +271,345 @@ void QCocoaBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const #endif } +// ---------------------------------------------------------------------------- + +// 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) +{ + qCDebug(lcQpaBackingStore) << "Creating QCALayerBackingStore for" << window; + m_buffers.resize(1); +} + +QCALayerBackingStore::~QCALayerBackingStore() +{ +} + +void QCALayerBackingStore::resize(const QSize &size, const QRegion &staticContents) +{ + qCDebug(lcQpaBackingStore) << "Resize requested to" << size; + + if (!staticContents.isNull()) + qCWarning(lcQpaBackingStore) << "QCALayerBackingStore does not support static contents"; + + m_requestedSize = size; +} + +void QCALayerBackingStore::beginPaint(const QRegion ®ion) +{ + Q_UNUSED(region); + + QMacAutoReleasePool pool; + + qCInfo(lcQpaBackingStore) << "Beginning paint of" << region << "into backingstore of" << m_requestedSize; + + ensureBackBuffer(); // Find an unused back buffer, or reserve space for a new one + + const bool bufferWasRecreated = recreateBackBufferIfNeeded(); + + m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess); + + // Although undocumented, QBackingStore::beginPaint expects the painted region + // to be cleared before use if the window has a surface format with an alpha. + // Fresh IOSurfaces are already cleared, so we don't need to clear those. + if (!bufferWasRecreated && window()->format().hasAlpha()) { + qCDebug(lcQpaBackingStore) << "Clearing" << region << "before use"; + QPainter painter(m_buffers.back()->asImage()); + painter.setCompositionMode(QPainter::CompositionMode_Source); + for (const QRect &rect : region) + painter.fillRect(rect, Qt::transparent); + } + + m_paintedRegion += region; +} + +void QCALayerBackingStore::ensureBackBuffer() +{ + if (window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer) + return; + + // The current back buffer may have been assigned to a layer in a previous flush, + // but we deferred the swap. Do it now if the surface has been picked up by CA. + if (m_buffers.back() && m_buffers.back()->isInUse() && m_buffers.back() != m_buffers.front()) { + qCInfo(lcQpaBackingStore) << "Back buffer has been picked up by CA, swapping to front"; + std::swap(m_buffers.back(), m_buffers.front()); + } + + if (Q_UNLIKELY(lcQpaBackingStore().isDebugEnabled())) { + // ┌───────┬───────┬───────┬─────┬──────┐ + // │ front ┊ spare ┊ spare ┊ ... ┊ back │ + // └───────┴───────┴───────┴─────┴──────┘ + for (const auto &buffer : m_buffers) { + qCDebug(lcQpaBackingStore).nospace() << " " + << (buffer == m_buffers.front() ? "front" : + buffer == m_buffers.back() ? " back" : + "spare" + ) << ": " << buffer.get(); + } + } + + // Ensure our back buffer is ready to draw into. If not, find a buffer that + // is not in use, or reserve space for a new buffer if none can be found. + for (auto &buffer : backwards(m_buffers)) { + if (!buffer || !buffer->isInUse()) { + // Buffer is okey to use, swap if necessary + if (buffer != m_buffers.back()) + std::swap(buffer, m_buffers.back()); + qCDebug(lcQpaBackingStore) << "Using back buffer" << m_buffers.back().get(); + + static const int kMaxSwapChainDepth = 3; + if (m_buffers.size() > kMaxSwapChainDepth) { + qCDebug(lcQpaBackingStore) << "Reducing swap chain depth to" << kMaxSwapChainDepth; + m_buffers.erase(std::next(m_buffers.begin(), 1), std::prev(m_buffers.end(), 2)); + } + + break; + } else if (buffer == m_buffers.front()) { + // We've exhausted the available buffers, make room for a new one + const int swapChainDepth = m_buffers.size() + 1; + qCDebug(lcQpaBackingStore) << "Available buffers exhausted, increasing swap chain depth to" << swapChainDepth; + m_buffers.resize(swapChainDepth); + break; + } + } + + Q_ASSERT(!m_buffers.back() || !m_buffers.back()->isInUse()); +} + +// Disabled until performance issue on 5K iMac Pro has been investigated further, +// as rounding up during resize will typically result in full screen buffer sizes +// and low frame rate also for smaller window sizes. +#define USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE 0 + +bool QCALayerBackingStore::recreateBackBufferIfNeeded() +{ + const qreal devicePixelRatio = window()->devicePixelRatio(); + QSize requestedBufferSize = m_requestedSize * devicePixelRatio; + + const NSView *backingStoreView = static_cast<QCocoaWindow *>(window()->handle())->view(); + Q_UNUSED(backingStoreView); + + auto bufferSizeMismatch = [&](const QSize requested, const QSize actual) { +#if USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE + if (backingStoreView.inLiveResize) { + // Prevent over-eager buffer allocation during window resize by reusing larger buffers + return requested.width() > actual.width() || requested.height() > actual.height(); + } +#endif + return requested != actual; + }; + + if (!m_buffers.back() || bufferSizeMismatch(requestedBufferSize, m_buffers.back()->size())) { +#if USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE + if (backingStoreView.inLiveResize) { + // Prevent over-eager buffer allocation during window resize by rounding up + QSize nativeScreenSize = window()->screen()->geometry().size() * devicePixelRatio; + requestedBufferSize = QSize(qNextPowerOfTwo(requestedBufferSize.width()), + qNextPowerOfTwo(requestedBufferSize.height())).boundedTo(nativeScreenSize); + } +#endif + + qCInfo(lcQpaBackingStore) << "Creating surface of" << requestedBufferSize + << "based on requested" << m_requestedSize << "and dpr =" << devicePixelRatio; + + static auto pixelFormat = QImage::toPixelFormat(QImage::Format_ARGB32_Premultiplied); + + NSView *view = static_cast<QCocoaWindow *>(window()->handle())->view(); + auto colorSpace = QCFType<CGColorSpaceRef>::constructFromGet(view.window.screen.colorSpace.CGColorSpace); + + m_buffers.back().reset(new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat, colorSpace)); + return true; + } + + return false; +} + +QPaintDevice *QCALayerBackingStore::paintDevice() +{ + Q_ASSERT(m_buffers.back()); + return m_buffers.back()->asImage(); +} + +void QCALayerBackingStore::endPaint() +{ + qCInfo(lcQpaBackingStore) << "Paint ended with painted region" << m_paintedRegion; + m_buffers.back()->unlock(); +} + +void QCALayerBackingStore::flush(QWindow *flushedWindow, const QRegion ®ion, const QPoint &offset) +{ + Q_UNUSED(region); + Q_UNUSED(offset); + + if (!prepareForFlush()) + return; + + QMacAutoReleasePool pool; + + NSView *backingStoreView = static_cast<QCocoaWindow *>(window()->handle())->view(); + NSView *flushedView = static_cast<QCocoaWindow *>(flushedWindow->handle())->view(); + + id backBufferSurface = (__bridge id)m_buffers.back()->surface(); + if (flushedView.layer.contents == backBufferSurface) { + // We've managed to paint to the back buffer again before Core Animation had time + // to flush the transaction and persist the layer changes to the window server. + // The layer already knows about the back buffer, and we don't need to re-apply + // it to pick up the surface changes, so bail out early. + qCInfo(lcQpaBackingStore).nospace() << "Skipping flush of " << flushedView + << ", layer already reflects back buffer"; + return; + } + + // Trigger a new display cycle if there isn't one. This ensures that our layer updates + // are committed as part of a display-cycle instead of on the next runloop pass. This + // means CA won't try to throttle us if we flush too fast, and we'll coalesce our flush + // with other pending view and layer updates. + backingStoreView.window.viewsNeedDisplay = YES; + + if (window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer) { + // The private API [CALayer reloadValueForKeyPath:@"contents"] would be preferable, + // but barring any side effects or performance issues we opt for the hammer for now. + flushedView.layer.contents = nil; + } + + qCInfo(lcQpaBackingStore) << "Flushing" << backBufferSurface + << "to" << flushedView.layer << "of" << flushedView; + + flushedView.layer.contents = backBufferSurface; + + if (flushedView != backingStoreView) { + const CGSize backingStoreSize = backingStoreView.bounds.size; + flushedView.layer.contentsRect = CGRectApplyAffineTransform( + [flushedView convertRect:flushedView.bounds toView:backingStoreView], + // The contentsRect is in unit coordinate system + CGAffineTransformMakeScale(1.0 / backingStoreSize.width, 1.0 / backingStoreSize.height)); + } + + // Since we may receive multiple flushes before a new frame is started, we do not + // swap any buffers just yet. Instead we check in the next beginPaint if the layer's + // surface is in use, and if so swap to an unused surface as the new back buffer. + + // Note: Ideally CoreAnimation would mark a surface as in use the moment we assign + // it to a layer, but as that's not the case we may end up painting to the same back + // buffer once more if we are painting faster than CA can ship the surfaces over to + // the window server. +} + +void QCALayerBackingStore::composeAndFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, + QPlatformTextureList *textures, bool translucentBackground) +{ + if (!prepareForFlush()) + return; + + QPlatformBackingStore::composeAndFlush(window, region, offset, textures, translucentBackground); +} + +QPlatformGraphicsBuffer *QCALayerBackingStore::graphicsBuffer() const +{ + return m_buffers.back().get(); +} + +bool QCALayerBackingStore::prepareForFlush() +{ + if (!m_buffers.back()) { + qCWarning(lcQpaBackingStore) << "Tried to flush backingstore without painting to it first"; + return false; + } + + // Update dirty state of buffers based on what was painted. The back buffer will be + // less dirty, since we painted to it, while other buffers will become more dirty. + // This allows us to minimize copies between front and back buffers on swap in the + // cases where the painted region overlaps with the previous frame (front buffer). + for (const auto &buffer : m_buffers) { + if (buffer == m_buffers.back()) + buffer->dirtyRegion -= m_paintedRegion; + else + buffer->dirtyRegion += m_paintedRegion; + } + + // After painting, the back buffer is only guaranteed to have content for the painted + // region, and may still have dirty areas that need to be synced up with the front buffer, + // if we have one. We know that the front buffer is always up to date. + if (!m_buffers.back()->dirtyRegion.isEmpty() && m_buffers.front() != m_buffers.back()) { + QRegion preserveRegion = m_buffers.back()->dirtyRegion; + qCDebug(lcQpaBackingStore) << "Preserving" << preserveRegion << "from front to back buffer"; + + m_buffers.front()->lock(QPlatformGraphicsBuffer::SWReadAccess); + const QImage *frontBuffer = m_buffers.front()->asImage(); + + const QRect frontSurfaceBounds(QPoint(0, 0), m_buffers.front()->size()); + const qreal sourceDevicePixelRatio = frontBuffer->devicePixelRatio(); + + m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess); + QPainter painter(m_buffers.back()->asImage()); + painter.setCompositionMode(QPainter::CompositionMode_Source); + + // Let painter operate in device pixels, to make it easier to compare coordinates + const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio(); + painter.scale(1.0 / targetDevicePixelRatio, 1.0 / targetDevicePixelRatio); + + for (const QRect &rect : preserveRegion) { + QRect sourceRect(rect.topLeft() * sourceDevicePixelRatio, rect.size() * sourceDevicePixelRatio); + QRect targetRect(rect.topLeft() * targetDevicePixelRatio, rect.size() * targetDevicePixelRatio); + +#ifdef QT_DEBUG + if (Q_UNLIKELY(!frontSurfaceBounds.contains(sourceRect.bottomRight()))) { + qCWarning(lcQpaBackingStore) << "Front buffer too small to preserve" + << QRegion(sourceRect).subtracted(frontSurfaceBounds); + } +#endif + painter.drawImage(targetRect, *frontBuffer, sourceRect); + } + + m_buffers.back()->unlock(); + m_buffers.front()->unlock(); + + // The back buffer is now completely in sync, ready to be presented + m_buffers.back()->dirtyRegion = QRegion(); + } + + // Prepare for another round of painting + m_paintedRegion = QRegion(); + + return true; +} + +// ---------------------------------------------------------------------------- + +QCALayerBackingStore::GraphicsBuffer::GraphicsBuffer(const QSize &size, qreal devicePixelRatio, + const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace) + : QIOSurfaceGraphicsBuffer(size, format, colorSpace) + , dirtyRegion(0, 0, size.width() / devicePixelRatio, size.height() / devicePixelRatio) + , m_devicePixelRatio(devicePixelRatio) +{ +} + +QImage *QCALayerBackingStore::GraphicsBuffer::asImage() +{ + if (m_image.isNull()) { + qCDebug(lcQpaBackingStore) << "Setting up paint device for" << this; + CFRetain(surface()); + m_image = QImage(data(), size().width(), size().height(), + bytesPerLine(), QImage::toImageFormat(format()), + QImageCleanupFunction(CFRelease), surface()); + m_image.setDevicePixelRatio(m_devicePixelRatio); + } + + Q_ASSERT_X(m_image.constBits() == data(), "QCALayerBackingStore", + "IOSurfaces should have have a fixed location in memory once created"); + + return &m_image; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h index ebf33cf4e2..9771cd0289 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h @@ -168,10 +168,10 @@ public: uint processEventsCalled; NSModalSession currentModalSessionCached; NSModalSession currentModalSession(); - void updateChildrenWorksWhenModal(); void temporarilyStopAllModalSessions(); void beginModalSession(QWindow *widget); void endModalSession(QWindow *widget); + bool hasModalSession() const; void cleanupModalSessions(); void cancelWaitForMoreEvents(); diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm index b0f2b6d940..84ffadea83 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm @@ -672,43 +672,9 @@ NSModalSession QCocoaEventDispatcherPrivate::currentModalSession() return currentModalSessionCached; } -static void setChildrenWorksWhenModal(QWindow *window, bool worksWhenModal) +bool QCocoaEventDispatcherPrivate::hasModalSession() const { - Q_UNUSED(window) - Q_UNUSED(worksWhenModal) - - // For NSPanels (but not NSWindows, sadly), we can set the flag - // worksWhenModal, so that they are active even when they are not modal. -/* - ### not ported - QList<QDialog *> dialogs = window->findChildren<QDialog *>(); - for (int i=0; i<dialogs.size(); ++i){ - NSWindow *window = qt_mac_window_for(dialogs[i]); - if (window && [window isKindOfClass:[NSPanel class]]) { - [static_cast<NSPanel *>(window) setWorksWhenModal:worksWhenModal]; - if (worksWhenModal && [window isVisible]){ - [window orderFront:window]; - } - } - } -*/ -} - -void QCocoaEventDispatcherPrivate::updateChildrenWorksWhenModal() -{ - // Make the dialog children of the window - // active. And make the dialog children of - // the previous modal dialog unactive again: - QMacAutoReleasePool pool; - int size = cocoaModalSessionStack.size(); - if (size > 0){ - if (QWindow *prevModal = cocoaModalSessionStack[size-1].window) - setChildrenWorksWhenModal(prevModal, true); - if (size > 1){ - if (QWindow *prevModal = cocoaModalSessionStack[size-2].window) - setChildrenWorksWhenModal(prevModal, false); - } - } + return !cocoaModalSessionStack.isEmpty(); } void QCocoaEventDispatcherPrivate::cleanupModalSessions() @@ -743,7 +709,6 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions() cocoaModalSessionStack.remove(i); } - updateChildrenWorksWhenModal(); cleanupModalSessionsNeeded = false; } @@ -764,7 +729,6 @@ void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window) // stopped in cleanupModalSessions()). QCocoaModalSessionInfo info = {window, nullptr, nullptr}; cocoaModalSessionStack.push(info); - updateChildrenWorksWhenModal(); currentModalSessionCached = nullptr; } diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index e7243ec250..d1695ea860 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -55,7 +55,6 @@ #include <qbuffer.h> #include <qdebug.h> #include <qstringlist.h> -#include <qtextcodec.h> #include <qvarlengtharray.h> #include <stdlib.h> #include <qabstracteventdispatcher.h> diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 1e1b3907ed..fe1fc31553 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -86,7 +86,7 @@ QCocoaGLContext::QCocoaGLContext(QOpenGLContext *context) } m_context = nativeHandle.value<QCocoaNativeContext>().context(); if (!m_context) { - qCWarning(lcQpaOpenGLContext, "QCocoaNativeContext's NSOpenGLContext can not be null"); + qCWarning(lcQpaOpenGLContext, "QCocoaNativeContext's NSOpenGLContext cannot be null"); return; } @@ -216,8 +216,13 @@ NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurface << NSOpenGLPFASamples << NSOpenGLPixelFormatAttribute(format.samples()); } - // Allow rendering on GPUs without a connected display - attrs << NSOpenGLPFAAllowOfflineRenderers; + //Workaround for problems with Chromium and offline renderers on the lat 2013 MacPros. + //FIXME: Think if this could be solved via QSurfaceFormat in the future. + static bool offlineRenderersAllowed = qEnvironmentVariableIsEmpty("QT_MAC_PRO_WEBENGINE_WORKAROUND"); + if (offlineRenderersAllowed) { + // Allow rendering on GPUs without a connected display + attrs << NSOpenGLPFAAllowOfflineRenderers; + } // FIXME: Pull this information out of the NSView QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER"); @@ -414,7 +419,8 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface) // have the same effect as an update. // Now we are ready to associate the view with the context - if ((m_context.view = view) != view) { + m_context.view = view; + if (m_context.view != view) { qCInfo(lcQpaOpenGLContext) << "Failed to set" << view << "as drawable for" << m_context; m_updateObservers.clear(); return false; diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h index 953bf331bb..69aa7937b6 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.h +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -52,6 +52,7 @@ // #include "qt_mac_p.h" #include <private/qguiapplication_p.h> +#include <QtCore/qoperatingsystemversion.h> #include <QtGui/qpalette.h> #include <QtGui/qscreen.h> @@ -60,6 +61,8 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSView)); +struct mach_header; + QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow) @@ -173,6 +176,34 @@ T qt_mac_resolveOption(const T &fallback, QWindow *window, const QByteArray &pro return fallback; } +// ------------------------------------------------------------------------- + +#if !defined(Q_PROCESSOR_X86_64) +#error "32-bit builds are not supported" +#endif + +class QMacVersion +{ +public: + enum VersionTarget { + ApplicationBinary, + QtLibraries + }; + + static QOperatingSystemVersion buildSDK(VersionTarget target = ApplicationBinary); + static QOperatingSystemVersion deploymentTarget(VersionTarget target = ApplicationBinary); + static QOperatingSystemVersion currentRuntime(); + +private: + QMacVersion() = default; + using VersionTuple = QPair<QOperatingSystemVersion, QOperatingSystemVersion>; + static VersionTuple versionsForImage(const mach_header *machHeader); + static VersionTuple applicationVersion(); + static VersionTuple libraryVersion(); +}; + +// ------------------------------------------------------------------------- + QT_END_NAMESPACE // @compatibility_alias doesn't work with protocols diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index 0f5ddfa49a..9c705616ba 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -55,11 +55,14 @@ #include <algorithm> +#include <mach-o/dyld.h> +#include <dlfcn.h> + 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"); +Q_LOGGING_CATEGORY(lcQpaMouse, "qt.qpa.input.mouse", QtCriticalMsg); Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen"); // @@ -368,6 +371,92 @@ QString qt_mac_removeAmpersandEscapes(QString s) return QPlatformTheme::removeMnemonics(s).trimmed(); } +// ------------------------------------------------------------------------- + +#if !defined(Q_PROCESSOR_X86_64) +#error "32-bit builds are not supported" +#endif + +QOperatingSystemVersion QMacVersion::buildSDK(VersionTarget target) +{ + switch (target) { + case ApplicationBinary: return applicationVersion().second; + case QtLibraries: return libraryVersion().second; + } + Q_UNREACHABLE(); +} + +QOperatingSystemVersion QMacVersion::deploymentTarget(VersionTarget target) +{ + switch (target) { + case ApplicationBinary: return applicationVersion().first; + case QtLibraries: return libraryVersion().first; + } + Q_UNREACHABLE(); +} + +QOperatingSystemVersion QMacVersion::currentRuntime() +{ + return QOperatingSystemVersion::current(); +} + +QMacVersion::VersionTuple QMacVersion::versionsForImage(const mach_header *machHeader) +{ + static auto makeVersionTuple = [](uint32_t dt, uint32_t sdk) { + return qMakePair( + QOperatingSystemVersion(QOperatingSystemVersion::MacOS, + dt >> 16 & 0xffff, dt >> 8 & 0xff, dt & 0xff), + QOperatingSystemVersion(QOperatingSystemVersion::MacOS, + sdk >> 16 & 0xffff, sdk >> 8 & 0xff, sdk & 0xff) + ); + }; + + auto commandCursor = uintptr_t(machHeader) + sizeof(mach_header_64); + for (uint32_t i = 0; i < machHeader->ncmds; ++i) { + load_command *loadCommand = reinterpret_cast<load_command *>(commandCursor); + if (loadCommand->cmd == LC_VERSION_MIN_MACOSX) { + auto versionCommand = reinterpret_cast<version_min_command *>(loadCommand); + return makeVersionTuple(versionCommand->version, versionCommand->sdk); +#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13) + } else if (loadCommand->cmd == LC_BUILD_VERSION) { + auto versionCommand = reinterpret_cast<build_version_command *>(loadCommand); + return makeVersionTuple(versionCommand->minos, versionCommand->sdk); +#endif + } + commandCursor += loadCommand->cmdsize; + } + Q_ASSERT_X(false, "QCocoaIntegration", "Could not find any version load command"); + Q_UNREACHABLE(); +} + +QMacVersion::VersionTuple QMacVersion::applicationVersion() +{ + static VersionTuple version = []() { + const mach_header *executableHeader = nullptr; + for (uint32_t i = 0; i < _dyld_image_count(); ++i) { + auto header = _dyld_get_image_header(i); + if (header->filetype == MH_EXECUTE) { + executableHeader = header; + break; + } + } + Q_ASSERT_X(executableHeader, "QCocoaIntegration", "Failed to resolve Mach-O header of executable"); + return versionsForImage(executableHeader); + }(); + return version; +} + +QMacVersion::VersionTuple QMacVersion::libraryVersion() +{ + static VersionTuple version = []() { + Dl_info cocoaPluginImage; + dladdr((const void *)&QMacVersion::libraryVersion, &cocoaPluginImage); + Q_ASSERT_X(cocoaPluginImage.dli_fbase, "QCocoaIntegration", "Failed to resolve Mach-O header of Cocoa plugin"); + return versionsForImage(static_cast<mach_header*>(cocoaPluginImage.dli_fbase)); + }(); + return version; +} + QT_END_NAMESPACE /*! \internal diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index 7de7e073de..04cb4e1226 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -144,6 +144,7 @@ private: #endif QScopedPointer<QPlatformTheme> mPlatformTheme; QList<QCocoaScreen *> mScreens; + QMacScopedObserver m_screensObserver; #ifndef QT_NO_CLIPBOARD QCocoaClipboard *mCocoaClipboard; #endif diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 612290c9bd..fb3d05d3e4 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -63,6 +63,8 @@ #include <QtGui/private/qcoregraphics_p.h> +#include <QtFontDatabaseSupport/private/qfontengine_coretext_p.h> + #ifdef QT_WIDGETS_LIB #include <QtWidgets/qtwidgetsglobal.h> #if QT_CONFIG(filedialog) @@ -79,6 +81,32 @@ static void initResources() QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcQpa, "qt.qpa", QtWarningMsg); + +static void logVersionInformation() +{ + if (!lcQpa().isInfoEnabled()) + return; + + auto osVersion = QMacVersion::currentRuntime(); + auto qtBuildSDK = QMacVersion::buildSDK(QMacVersion::QtLibraries); + auto qtDeploymentTarget = QMacVersion::deploymentTarget(QMacVersion::QtLibraries); + auto appBuildSDK = QMacVersion::buildSDK(QMacVersion::ApplicationBinary); + auto appDeploymentTarget = QMacVersion::deploymentTarget(QMacVersion::ApplicationBinary); + + qCInfo(lcQpa, "Loading macOS (Cocoa) platform plugin for Qt " QT_VERSION_STR ", running on macOS %d.%d.%d\n\n" \ + " Component SDK version Deployment target \n" \ + " ------------- ------------- -------------------\n" \ + " Qt " QT_VERSION_STR " %d.%d.%d %d.%d.%d\n" \ + " Application %d.%d.%d %d.%d.%d\n", + osVersion.majorVersion(), osVersion.minorVersion(), osVersion.microVersion(), + qtBuildSDK.majorVersion(), qtBuildSDK.minorVersion(), qtBuildSDK.microVersion(), + qtDeploymentTarget.majorVersion(), qtDeploymentTarget.minorVersion(), qtDeploymentTarget.microVersion(), + appBuildSDK.majorVersion(), appBuildSDK.minorVersion(), appBuildSDK.microVersion(), + appDeploymentTarget.majorVersion(), appDeploymentTarget.minorVersion(), appDeploymentTarget.microVersion()); +} + + class QCoreTextFontEngine; class QFontEngineFT; @@ -112,6 +140,8 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) , mServices(new QCocoaServices) , mKeyboardMapper(new QCocoaKeyMapper) { + logVersionInformation(); + if (mInstance) qWarning("Creating multiple Cocoa platform integrations is not supported"); mInstance = this; @@ -176,6 +206,9 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) // by explicitly setting the presentation option to the magic 'default value', // which will resolve to an actual value and result in screen invalidation. cocoaApplication.presentationOptions = NSApplicationPresentationDefault; + + m_screensObserver = QMacScopedObserver([NSApplication sharedApplication], + NSApplicationDidChangeScreenParametersNotification, [&]() { updateScreens(); }); updateScreens(); QMacInternalPasteboardMime::initializeMimeTypes(); @@ -211,7 +244,7 @@ QCocoaIntegration::~QCocoaIntegration() // Delete screens in reverse order to avoid crash in case of multiple screens while (!mScreens.isEmpty()) { - destroyScreen(mScreens.takeLast()); + QWindowSystemInterface::handleScreenRemoved(mScreens.takeLast()); } clearToolbars(); @@ -271,7 +304,7 @@ void QCocoaIntegration::updateScreens() screen = new QCocoaScreen(i); mScreens.append(screen); qCDebug(lcQpaScreen) << "Adding" << screen; - screenAdded(screen); + QWindowSystemInterface::handleScreenAdded(screen); } siblings << screen; } @@ -288,7 +321,7 @@ void QCocoaIntegration::updateScreens() // Prevent stale references to NSScreen during destroy screen->m_screenIndex = -1; qCDebug(lcQpaScreen) << "Removing" << screen; - destroyScreen(screen); + QWindowSystemInterface::handleScreenRemoved(screen); } } @@ -312,12 +345,17 @@ QCocoaScreen *QCocoaIntegration::screenForNSScreen(NSScreen *nsScreen) bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) const { switch (cap) { - case ThreadedPixmaps: #ifndef QT_NO_OPENGL - case OpenGL: case ThreadedOpenGL: + // AppKit expects rendering to happen on the main thread, and we can + // easily end up in situations where rendering on secondary threads + // will result in visual artifacts, bugs, or even deadlocks, when + // building with SDK 10.14 or higher which enbles view layer-backing. + return QMacVersion::buildSDK() < QOperatingSystemVersion(QOperatingSystemVersion::MacOSMojave); + case OpenGL: case BufferQueueingOpenGL: #endif + case ThreadedPixmaps: case WindowMasks: case MultipleWindows: case ForeignWindows: @@ -369,7 +407,16 @@ QPlatformOpenGLContext *QCocoaIntegration::createPlatformOpenGLContext(QOpenGLCo QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *window) const { - return new QCocoaBackingStore(window); + QCocoaWindow *platformWindow = static_cast<QCocoaWindow*>(window->handle()); + if (!platformWindow) { + qWarning() << window << "must be created before being used with a backingstore"; + return nullptr; + } + + if (platformWindow->view().layer) + return new QCALayerBackingStore(window); + else + return new QNSWindowBackingStore(window); } QAbstractEventDispatcher *QCocoaIntegration::createEventDispatcher() const @@ -444,7 +491,7 @@ QCocoaServices *QCocoaIntegration::services() const QVariant QCocoaIntegration::styleHint(StyleHint hint) const { if (hint == QPlatformIntegration::FontSmoothingGamma) - return 2.0; + return QCoreTextFontEngine::fontSmoothingGamma(); return QPlatformIntegration::styleHint(hint); } diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index 7b96fca3f9..f34988721d 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -250,6 +250,9 @@ void QCocoaMenu::syncMenuItem_helper(QPlatformMenuItem *menuItem, bool menubarUp if (wasMerged) { oldItem.enabled = NO; oldItem.hidden = YES; + oldItem.keyEquivalent = @""; + oldItem.keyEquivalentModifierMask = NSEventModifierFlagCommand; + } else { [m_nativeMenu removeItem:oldItem]; } diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h index 20fc741fb8..c842b08d52 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.h +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h @@ -119,7 +119,6 @@ private: QString m_text; QIcon m_icon; QPointer<QCocoaMenu> m_menu; - QFont m_font; MenuRole m_role; MenuRole m_detectedRole; #ifndef QT_NO_SHORTCUT diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm index 21faa6d985..e54b6284e5 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm @@ -173,7 +173,7 @@ void QCocoaMenuItem::setIsSeparator(bool isSeparator) void QCocoaMenuItem::setFont(const QFont &font) { - m_font = font; + Q_UNUSED(font) } void QCocoaMenuItem::setRole(MenuRole role) @@ -319,21 +319,7 @@ NSMenuItem *QCocoaMenuItem::sync() text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")"); #endif - QString finalString = QPlatformTheme::removeMnemonics(text); - bool useAttributedTitle = false; - // Cocoa Font and title - if (m_font.resolve()) { - NSFont *customMenuFont = [NSFont fontWithName:m_font.family().toNSString() - size:m_font.pointSize()]; - if (customMenuFont) { - NSAttributedString *str = [[[NSAttributedString alloc] initWithString:finalString.toNSString() - attributes:@{NSFontAttributeName: customMenuFont}] autorelease]; - m_native.attributedTitle = str; - useAttributedTitle = true; - } - } - if (!useAttributedTitle) - m_native.title = finalString.toNSString(); + m_native.title = QPlatformTheme::removeMnemonics(text).toNSString(); #ifndef QT_NO_SHORTCUT if (accel.count() == 1) { diff --git a/src/plugins/platforms/cocoa/qcocoaprintdevice.h b/src/plugins/platforms/cocoa/qcocoaprintdevice.h index 20b27f3286..d267343b0e 100644 --- a/src/plugins/platforms/cocoa/qcocoaprintdevice.h +++ b/src/plugins/platforms/cocoa/qcocoaprintdevice.h @@ -98,7 +98,9 @@ protected: void loadOutputBins() const override; void loadDuplexModes() const override; void loadColorModes() const override; +#if QT_CONFIG(mimetype) void loadMimeTypes() const override; +#endif private: QPageSize createPageSize(const PMPaper &paper) const; diff --git a/src/plugins/platforms/cocoa/qcocoaprintdevice.mm b/src/plugins/platforms/cocoa/qcocoaprintdevice.mm index 24ec7ca9a4..7605dc9d1a 100644 --- a/src/plugins/platforms/cocoa/qcocoaprintdevice.mm +++ b/src/plugins/platforms/cocoa/qcocoaprintdevice.mm @@ -39,7 +39,9 @@ #include "qcocoaprintdevice.h" +#if QT_CONFIG(mimetype) #include <QtCore/qmimedatabase.h> +#endif #include <qdebug.h> QT_BEGIN_NAMESPACE @@ -417,6 +419,7 @@ QPrint::ColorMode QCocoaPrintDevice::defaultColorMode() const return QPrint::GrayScale; } +#if QT_CONFIG(mimetype) void QCocoaPrintDevice::loadMimeTypes() const { // TODO Check how settings affect returned list @@ -438,6 +441,7 @@ void QCocoaPrintDevice::loadMimeTypes() const } m_haveMimeTypes = true; } +#endif // mimetype bool QCocoaPrintDevice::openPpdFile() { diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm index f82ef202b1..6a5b0e6e3e 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.mm +++ b/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -269,15 +269,14 @@ struct DeferredDebugHelper void QCocoaScreen::deliverUpdateRequests() { - if (!QGuiApplication::instance()) - return; + QMacAutoReleasePool pool; // The CVDisplayLink callback is a notification that it's a good time to produce a new frame. // Since the callback is delivered on a separate thread we have to marshal it over to the // main thread, as Qt requires update requests to be delivered there. This needs to happen // asynchronously, as otherwise we may end up deadlocking if the main thread calls back // into any of the CVDisplayLink APIs. - if (QThread::currentThread() != QGuiApplication::instance()->thread()) { + if (!NSThread.isMainThread) { // We're explicitly not using the data of the GCD source to track the pending updates, // as the data isn't reset to 0 until after the event handler, and also doesn't update // during the event handler, both of which we need to track late frames. @@ -330,7 +329,7 @@ void QCocoaScreen::deliverUpdateRequests() auto windows = QGuiApplication::allWindows(); for (int i = 0; i < windows.size(); ++i) { QWindow *window = windows.at(i); - QPlatformWindow *platformWindow = window->handle(); + auto *platformWindow = static_cast<QCocoaWindow*>(window->handle()); if (!platformWindow) continue; @@ -341,7 +340,7 @@ void QCocoaScreen::deliverUpdateRequests() continue; // Skip windows that are not doing update requests via display link - if (!(window->format().swapInterval() > 0)) + if (!platformWindow->updatesWithDisplayLink()) continue; platformWindow->deliverUpdateRequest(); @@ -411,8 +410,7 @@ QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) continue; - id<QNSWindowProtocol> proto = static_cast<id<QNSWindowProtocol> >(nsWindow); - QCocoaWindow *cocoaWindow = proto.platformWindow; + QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow; if (!cocoaWindow) continue; window = cocoaWindow->window(); @@ -428,65 +426,71 @@ QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const return window; } -QPixmap QCocoaScreen::grabWindow(WId window, int x, int y, int width, int height) const +QPixmap QCocoaScreen::grabWindow(WId view, int x, int y, int width, int height) const { - // TODO window should be handled - Q_UNUSED(window) - - const int maxDisplays = 128; // 128 displays should be enough for everyone. + // 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 + // only, since its NSWindow has the NSWindowStyleMaskTitled flag set. + Q_UNUSED(view); + QRect grabRect = QRect(x, y, width, height); + qCDebug(lcQpaScreen) << "input grab rect" << grabRect; + + // Find which displays to grab from, or all of them if the grab size is unspecified + const int maxDisplays = 128; CGDirectDisplayID displays[maxDisplays]; CGDisplayCount displayCount; - CGRect cgRect; - - if (width < 0 || height < 0) { - // get all displays - cgRect = CGRectInfinite; - } else { - cgRect = CGRectMake(x, y, width, height); - } + CGRect cgRect = (width < 0 || height < 0) ? CGRectInfinite : grabRect.toCGRect(); const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount); - - if (err && displayCount == 0) + if (err || displayCount == 0) return QPixmap(); - // calculate pixmap size - QSize windowSize(width, height); + // If the grab size is not specified, set it to be the bounding box of all screens, if (width < 0 || height < 0) { QRect windowRect; for (uint i = 0; i < displayCount; ++i) { - const CGRect cgRect = CGDisplayBounds(displays[i]); - QRect qRect(cgRect.origin.x, cgRect.origin.y, cgRect.size.width, cgRect.size.height); - windowRect = windowRect.united(qRect); + QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(displays[i])).toRect(); + windowRect = windowRect.united(displayBounds); } - if (width < 0) - windowSize.setWidth(windowRect.width()); - if (height < 0) - windowSize.setHeight(windowRect.height()); + if (grabRect.width() < 0) + grabRect.setWidth(windowRect.width()); + if (grabRect.height() < 0) + grabRect.setHeight(windowRect.height()); } - const qreal dpr = devicePixelRatio(); - QPixmap windowPixmap(windowSize * dpr); - windowPixmap.fill(Qt::transparent); + qCDebug(lcQpaScreen) << "final grab rect" << grabRect << "from" << displayCount << "displays"; + // Grab images from each display + QVector<QImage> images; + QVector<QRect> destinations; for (uint i = 0; i < displayCount; ++i) { - const CGRect bounds = CGDisplayBounds(displays[i]); - - // Calculate the position and size of the requested area - QPoint pos(qAbs(bounds.origin.x - x), qAbs(bounds.origin.y - y)); - QSize size(qMin(pos.x() + width, qRound(bounds.size.width)), - qMin(pos.y() + height, qRound(bounds.size.height))); - pos *= dpr; - size *= dpr; - - // Take the whole screen and crop it afterwards, because CGDisplayCreateImageForRect - // has a strange behavior when mixing highDPI and non-highDPI displays - QCFType<CGImageRef> cgImage = CGDisplayCreateImage(displays[i]); - const QImage image = qt_mac_toQImage(cgImage); - - // Draw into windowPixmap only the requested size - QPainter painter(&windowPixmap); - painter.drawImage(windowPixmap.rect(), image, QRect(pos, size)); + auto display = displays[i]; + QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(display)).toRect(); + QRect grabBounds = displayBounds.intersected(grabRect); + QRect displayLocalGrabBounds = QRect(QPoint(grabBounds.topLeft() - displayBounds.topLeft()), grabBounds.size()); + QImage displayImage = qt_mac_toQImage(QCFType<CGImageRef>(CGDisplayCreateImageForRect(display, displayLocalGrabBounds.toCGRect()))); + displayImage.setDevicePixelRatio(displayImage.size().width() / displayLocalGrabBounds.size().width()); + images.append(displayImage); + QRect destBounds = QRect(QPoint(grabBounds.topLeft() - grabRect.topLeft()), grabBounds.size()); + destinations.append(destBounds); + qCDebug(lcQpaScreen) << "grab display" << i << "global" << grabBounds << "local" << displayLocalGrabBounds + << "grab image size" << displayImage.size() << "devicePixelRatio" << displayImage.devicePixelRatio(); } + + // Determine the highest dpr, which becomes the dpr for the returned pixmap. + qreal dpr = 1.0; + for (uint i = 0; i < displayCount; ++i) + dpr = qMax(dpr, images.at(i).devicePixelRatio()); + + // Alocate 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); + windowPixmap.fill(Qt::transparent); + QPainter painter(&windowPixmap); + for (uint i = 0; i < displayCount; ++i) + painter.drawImage(destinations.at(i), images.at(i)); + return windowPixmap; } diff --git a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm index c1711e7cd4..9b6dc94d33 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm @@ -75,8 +75,9 @@ QPalette * qt_mac_createSystemPalette() palette->setBrush(QPalette::Disabled, QPalette::Text, dark); palette->setBrush(QPalette::Disabled, QPalette::ButtonText, dark); palette->setBrush(QPalette::Disabled, QPalette::Base, backgroundBrush); - palette->setBrush(QPalette::Active, QPalette::Base, backgroundBrush); - palette->setBrush(QPalette::Inactive, QPalette::Base, backgroundBrush); + QBrush textBackgroundBrush = qt_mac_toQBrush([NSColor textBackgroundColor]); + palette->setBrush(QPalette::Active, QPalette::Base, textBackgroundBrush); + palette->setBrush(QPalette::Inactive, QPalette::Base, textBackgroundBrush); palette->setColor(QPalette::Disabled, QPalette::Dark, QColor(191, 191, 191)); palette->setColor(QPalette::Active, QPalette::Dark, QColor(191, 191, 191)); palette->setColor(QPalette::Inactive, QPalette::Dark, QColor(191, 191, 191)); @@ -157,10 +158,13 @@ QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() pal.setColor(QPalette::Inactive, QPalette::WindowText, qc); pal.setColor(QPalette::Active, QPalette::HighlightedText, qc); pal.setColor(QPalette::Inactive, QPalette::HighlightedText, qc); + pal.setColor(QPalette::Active, QPalette::ButtonText, qc); + pal.setColor(QPalette::Inactive, QPalette::ButtonText, qc); qc = qt_mac_toQColor(mac_widget_colors[i].inactive); pal.setColor(QPalette::Disabled, QPalette::Text, qc); pal.setColor(QPalette::Disabled, QPalette::WindowText, qc); pal.setColor(QPalette::Disabled, QPalette::HighlightedText, qc); + pal.setColor(QPalette::Disabled, QPalette::ButtonText, qc); } if (mac_widget_colors[i].paletteRole == QPlatformTheme::MenuPalette || mac_widget_colors[i].paletteRole == QPlatformTheme::MenuBarPalette) { @@ -202,7 +206,7 @@ QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() qt_mac_toQBrush([NSColor unemphasizedSelectedTextColor])); } else { baseColors = [NSColor controlAlternatingRowBackgroundColors]; - activeHighlightColor = [NSColor selectedControlColor]; + activeHighlightColor = [NSColor alternateSelectedControlColor]; pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, pal.brush(QPalette::Active, QPalette::Text)); } diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm index 0158895441..4982f5ee05 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm @@ -105,6 +105,8 @@ QT_USE_NAMESPACE @end @interface QT_MANGLE_NAMESPACE(QNSImageView) : NSImageView +@property (nonatomic, assign) BOOL down; +@property (nonatomic, assign) QT_MANGLE_NAMESPACE(QNSStatusItem) *parent; @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSStatusItem); @@ -277,36 +279,32 @@ QT_END_NAMESPACE @implementation NSStatusItem (Qt) @end -@implementation QNSImageView { - BOOL down; - QT_MANGLE_NAMESPACE(QNSStatusItem) *parent; -} - +@implementation QNSImageView - (instancetype)initWithParent:(QNSStatusItem *)myParent { self = [super init]; - parent = myParent; - down = NO; + self.parent = myParent; + self.down = NO; return self; } - (void)menuTrackingDone:(NSNotification *)__unused notification { - down = NO; + self.down = NO; [self setNeedsDisplay:YES]; } - (void)mousePressed:(NSEvent *)mouseEvent { - down = YES; + self.down = YES; int clickCount = [mouseEvent clickCount]; [self setNeedsDisplay:YES]; if (clickCount == 2) { [self menuTrackingDone:nil]; - [parent doubleClickSelector:self]; + [self.parent doubleClickSelector:self]; } else { - [parent triggerSelector:self button:cocoaButton2QtButton(mouseEvent)]; + [self.parent triggerSelector:self button:cocoaButton2QtButton(mouseEvent)]; } } @@ -344,7 +342,7 @@ QT_END_NAMESPACE } - (void)drawRect:(NSRect)rect { - [[parent item] drawStatusBarBackgroundInRect:rect withHighlight:down]; + [[self.parent item] drawStatusBarBackgroundInRect:rect withHighlight:self.down]; [super drawRect:rect]; } @end @@ -374,6 +372,7 @@ QT_END_NAMESPACE - (void)dealloc { [[NSStatusBar systemStatusBar] removeStatusItem:item]; [[NSNotificationCenter defaultCenter] removeObserver:imageCell]; + imageCell.parent = nil; [imageCell release]; [item release]; [super dealloc]; diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index a2229159b5..efe670abed 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -53,11 +53,13 @@ #include "qcocoahelpers.h" #include <QtCore/qfileinfo.h> +#include <QtGui/private/qfont_p.h> #include <QtGui/private/qguiapplication_p.h> #include <QtGui/private/qcoregraphics_p.h> #include <QtGui/qpainter.h> #include <QtGui/qtextformat.h> #include <QtFontDatabaseSupport/private/qcoretextfontdatabase_p.h> +#include <QtFontDatabaseSupport/private/qfontengine_coretext_p.h> #include <QtThemeSupport/private/qabstractfileiconengine_p.h> #include <qpa/qplatformdialoghelper.h> #include <qpa/qplatformintegration.h> @@ -78,12 +80,7 @@ #include <CoreServices/CoreServices.h> -#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) -@interface NSApplication (MojaveForwardDeclarations) -@property (readonly, strong) NSAppearance *effectiveAppearance NS_AVAILABLE_MAC(10_14); -@end -#endif - +#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) @interface QT_MANGLE_NAMESPACE(QCocoaThemeAppAppearanceObserver) : NSObject @property (readonly, nonatomic) QCocoaTheme *theme; - (instancetype)initWithTheme:(QCocoaTheme *)theme; @@ -122,6 +119,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaThemeAppAppearanceObserver); self.theme->handleSystemThemeChange(); } @end +#endif // QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) QT_BEGIN_NAMESPACE @@ -130,8 +128,10 @@ const char *QCocoaTheme::name = "cocoa"; QCocoaTheme::QCocoaTheme() : m_systemPalette(nullptr), m_appearanceObserver(nil) { +#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) m_appearanceObserver = [[QCocoaThemeAppAppearanceObserver alloc] initWithTheme:this]; +#endif [[NSNotificationCenter defaultCenter] addObserverForName:NSSystemColorsDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification *) { @@ -162,6 +162,11 @@ void QCocoaTheme::handleSystemThemeChange() m_systemPalette = qt_mac_createSystemPalette(); m_palettes = qt_mac_createRolePalettes(); + if (QCoreTextFontEngine::fontSmoothing() == QCoreTextFontEngine::FontSmoothing::Grayscale) { + // Re-populate glyph caches based on the new appearance's assumed text fill color + QFontCache::instance()->clear(); + } + QWindowSystemInterface::handleThemeChange(nullptr); } @@ -221,16 +226,12 @@ const QPalette *QCocoaTheme::palette(Palette type) const return nullptr; } -QHash<QPlatformTheme::Font, QFont *> qt_mac_createRoleFonts() -{ - QCoreTextFontDatabase *ctfd = static_cast<QCoreTextFontDatabase *>(QGuiApplicationPrivate::platformIntegration()->fontDatabase()); - return ctfd->themeFonts(); -} - const QFont *QCocoaTheme::font(Font type) const { if (m_fonts.isEmpty()) { - m_fonts = qt_mac_createRoleFonts(); + const auto *platformIntegration = QGuiApplicationPrivate::platformIntegration(); + const auto *coreTextFontDb = static_cast<QCoreTextFontDatabase *>(platformIntegration->fontDatabase()); + m_fonts = coreTextFontDb->themeFonts(); } return m_fonts.value(type, nullptr); } diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index 8f1bdb8af0..fef72bc496 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -129,6 +129,7 @@ public: bool isForeignWindow() const override; void requestUpdate() override; + bool updatesWithDisplayLink() const; void deliverUpdateRequest() override; void requestActivateWindow() override; @@ -252,7 +253,6 @@ public: // for QNSView bool m_needsInvalidateShadow; - bool m_hasModalSession; bool m_frameStrutEventsEnabled; QRect m_exposedRect; int m_registerTouchCount; diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index b2d1a80097..298d11fe08 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -153,7 +153,6 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) , m_inSetStyleMask(false) , m_menubar(nullptr) , m_needsInvalidateShadow(false) - , m_hasModalSession(false) , m_frameStrutEventsEnabled(false) , m_registerTouchCount(0) , m_resizableTransientParent(false) @@ -228,8 +227,9 @@ QSurfaceFormat QCocoaWindow::format() const // Upgrade the default surface format to include an alpha channel. The default RGB format // causes Cocoa to spend an unreasonable amount of time converting it to RGBA internally. - if (format == QSurfaceFormat()) + if (format.alphaBufferSize() < 0) format.setAlphaBufferSize(8); + return format; } @@ -303,13 +303,17 @@ void QCocoaWindow::setVisible(bool visible) { qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible; - m_inSetVisible = true; + QScopedValueRollback<bool> rollback(m_inSetVisible, true); QMacAutoReleasePool pool; QCocoaWindow *parentCocoaWindow = nullptr; if (window()->transientParent()) parentCocoaWindow = static_cast<QCocoaWindow *>(window()->transientParent()->handle()); + auto eventDispatcher = [] { + return static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(qApp->eventDispatcher())); + }; + if (visible) { // We need to recreate if the modality has changed as the style mask will need updating recreateWindowIfNeeded(); @@ -350,68 +354,46 @@ void QCocoaWindow::setVisible(bool visible) applyWindowState(window()->windowStates()); if (window()->windowState() != Qt::WindowMinimized) { - if ((window()->modality() == Qt::WindowModal - || window()->type() == Qt::Sheet) - && parentCocoaWindow) { - // show the window as a sheet + if (parentCocoaWindow && (window()->modality() == Qt::WindowModal || window()->type() == Qt::Sheet)) { + // Show the window as a sheet [parentCocoaWindow->nativeWindow() beginSheet:m_view.window completionHandler:nil]; - } else if (window()->modality() != Qt::NonModal) { - // show the window as application modal - QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - Q_ASSERT(cocoaEventDispatcher); - QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); - cocoaEventDispatcherPrivate->beginModalSession(window()); - m_hasModalSession = true; - } else if ([m_view.window canBecomeKeyWindow]) { - QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = nullptr; - if (cocoaEventDispatcher) - cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); - - if (cocoaEventDispatcherPrivate && cocoaEventDispatcherPrivate->cocoaModalSessionStack.isEmpty()) - [m_view.window makeKeyAndOrderFront:nil]; - else - [m_view.window orderFront:nil]; + } else if (window()->modality() == Qt::ApplicationModal) { + // Show the window as application modal + eventDispatcher()->beginModalSession(window()); + } else if (m_view.window.canBecomeKeyWindow && !eventDispatcher()->hasModalSession()) { + [m_view.window makeKeyAndOrderFront:nil]; } else { [m_view.window orderFront:nil]; } - // We want the events to properly reach the popup, dialog, and tool - if ((window()->type() == Qt::Popup || window()->type() == Qt::Dialog || window()->type() == Qt::Tool) - && [m_view.window isKindOfClass:[NSPanel class]]) { - ((NSPanel *)m_view.window).worksWhenModal = YES; - if (!(parentCocoaWindow && window()->transientParent()->isActive()) && window()->type() == Qt::Popup) { - removeMonitor(); - NSEventMask eventMask = NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown - | NSEventMaskOtherMouseDown | NSEventMaskMouseMoved; - monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *e) { - const auto button = cocoaButton2QtButton(e); - const auto buttons = currentlyPressedMouseButtons(); - const auto eventType = cocoaEvent2QtMouseEvent(e); - const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation); - const auto localPoint = window()->mapFromGlobal(globalPoint.toPoint()); - QWindowSystemInterface::handleMouseEvent(window(), localPoint, globalPoint, buttons, button, eventType); - }]; - } + // Close popup when clicking outside it + if (window()->type() == Qt::Popup && !(parentCocoaWindow && window()->transientParent()->isActive())) { + removeMonitor(); + NSEventMask eventMask = NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown + | NSEventMaskOtherMouseDown | NSEventMaskMouseMoved; + monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *e) { + const auto button = cocoaButton2QtButton(e); + const auto buttons = currentlyPressedMouseButtons(); + const auto eventType = cocoaEvent2QtMouseEvent(e); + const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation); + const auto localPoint = window()->mapFromGlobal(globalPoint.toPoint()); + QWindowSystemInterface::handleMouseEvent(window(), localPoint, globalPoint, buttons, button, eventType); + }]; } } } + // In some cases, e.g. QDockWidget, the content view is hidden before moving to its own // Cocoa window, and then shown again. Therefore, we test for the view being hidden even // if it's attached to an NSWindow. if ([m_view isHidden]) [m_view setHidden:NO]; + } else { - // qDebug() << "close" << this; - QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = nullptr; - if (cocoaEventDispatcher) - cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); + // Window not visible, hide it if (isContentView()) { - if (m_hasModalSession) { - if (cocoaEventDispatcherPrivate) - cocoaEventDispatcherPrivate->endModalSession(window()); - m_hasModalSession = false; + if (eventDispatcher()->hasModalSession()) { + eventDispatcher()->endModalSession(window()); } else { if ([m_view.window isSheet]) { Q_ASSERT_X(parentCocoaWindow, "QCocoaWindow", "Window modal dialog has no transient parent."); @@ -419,10 +401,14 @@ void QCocoaWindow::setVisible(bool visible) } } + // Note: We do not guard the order out by checking NSWindow.visible, as AppKit will + // in some cases, such as when hiding the application, order out and make a window + // invisible, but keep it in a list of "hidden windows", that it then restores again + // when the application is unhidden. We need to call orderOut explicitly, to bring + // the window out of this "hidden list". [m_view.window orderOut:nil]; - if (m_view.window == [NSApp keyWindow] - && !(cocoaEventDispatcherPrivate && cocoaEventDispatcherPrivate->currentModalSession())) { + if (m_view.window == [NSApp keyWindow] && !eventDispatcher()->hasModalSession()) { // Probably because we call runModalSession: outside [NSApp run] in QCocoaEventDispatcher // (e.g., when show()-ing a modal QDialog instead of exec()-ing it), it can happen that // the current NSWindow is still key after being ordered out. Then, after checking we @@ -434,6 +420,7 @@ void QCocoaWindow::setVisible(bool visible) } else { [m_view setHidden:YES]; } + removeMonitor(); if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) @@ -447,8 +434,6 @@ void QCocoaWindow::setVisible(bool visible) nativeParentWindow.styleMask |= NSWindowStyleMaskResizable; } } - - m_inSetVisible = false; } NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags) @@ -542,6 +527,12 @@ void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags) void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) { + // Updating the window flags may affect the window's theme frame, which + // in the process retains and then autoreleases the NSWindow. To make + // sure this doesn't leave lingering releases when there is no pool in + // place (e.g. during main(), before exec), we add one locally here. + QMacAutoReleasePool pool; + if (!isContentView()) return; @@ -628,7 +619,7 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) if (nsWindow.styleMask & NSWindowStyleMaskUtilityWindow && newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) { - qWarning() << window()->type() << "windows can not be made" << newState; + qWarning() << window()->type() << "windows cannot be made" << newState; handleWindowStateChanged(HandleUnconditionally); return; } @@ -892,34 +883,36 @@ void QCocoaWindow::raise() qCDebug(lcQpaWindow) << "QCocoaWindow::raise" << window(); // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm) - if (!isContentView()) - return; - - if (m_view.window.visible) { - { - // Clean up autoreleased temp objects from orderFront immediately. - // Failure to do so has been observed to cause leaks also beyond any outer - // autorelease pool (for example around a complete QWindow - // construct-show-raise-hide-delete cyle), counter to expected autoreleasepool - // behavior. - QMacAutoReleasePool pool; - [m_view.window orderFront:m_view.window]; - } - static bool raiseProcess = qt_mac_resolveOption(true, "QT_MAC_SET_RAISE_PROCESS"); - if (raiseProcess) { - [NSApp activateIgnoringOtherApps:YES]; + if (isContentView()) { + if (m_view.window.visible) { + { + // Clean up auto-released temp objects from orderFront immediately. + // Failure to do so has been observed to cause leaks also beyond any outer + // autorelease pool (for example around a complete QWindow + // construct-show-raise-hide-delete cycle), counter to expected autoreleasepool + // behavior. + QMacAutoReleasePool pool; + [m_view.window orderFront:m_view.window]; + } + static bool raiseProcess = qt_mac_resolveOption(true, "QT_MAC_SET_RAISE_PROCESS"); + if (raiseProcess) + [NSApp activateIgnoringOtherApps:YES]; } + } else { + [m_view.superview addSubview:m_view positioned:NSWindowAbove relativeTo:nil]; } } void QCocoaWindow::lower() { qCDebug(lcQpaWindow) << "QCocoaWindow::lower" << window(); - if (!isContentView()) - return; - if (m_view.window.visible) - [m_view.window orderBack:m_view.window]; + if (isContentView()) { + if (m_view.window.visible) + [m_view.window orderBack:m_view.window]; + } else { + [m_view.superview addSubview:m_view positioned:NSWindowBelow relativeTo:nil]; + } } bool QCocoaWindow::isExposed() const @@ -1093,6 +1086,9 @@ void QCocoaWindow::setEmbeddedInForeignView() void QCocoaWindow::viewDidChangeFrame() { + if (isContentView()) + return; // Handled below + handleGeometryChange(); } @@ -1373,11 +1369,14 @@ void QCocoaWindow::recreateWindowIfNeeded() if (m_windowModality != window()->modality()) recreateReason |= WindowModalityChanged; - const bool shouldBeContentView = !parentWindow && !isEmbeddedView; + Qt::WindowType type = window()->type(); + + const bool shouldBeContentView = !parentWindow + && !((type & Qt::SubWindow) == Qt::SubWindow) + && !isEmbeddedView; if (isContentView() != shouldBeContentView) recreateReason |= ContentViewChanged; - Qt::WindowType type = window()->type(); const bool isPanel = isContentView() && [m_view.window isKindOfClass:[QNSPanel class]]; const bool shouldBePanel = shouldBeContentView && ((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog); @@ -1462,11 +1461,10 @@ void QCocoaWindow::recreateWindowIfNeeded() void QCocoaWindow::requestUpdate() { - const int swapInterval = format().swapInterval(); - qCDebug(lcQpaDrawing) << "QCocoaWindow::requestUpdate" << window() << "swapInterval" << swapInterval; + qCDebug(lcQpaDrawing) << "QCocoaWindow::requestUpdate" << window() + << "using" << (updatesWithDisplayLink() ? "display-link" : "timer"); - if (swapInterval > 0) { - // Vsync is enabled, deliver via CVDisplayLink + if (updatesWithDisplayLink()) { static_cast<QCocoaScreen *>(screen())->requestUpdate(); } else { // Fall back to the un-throttled timer-based callback @@ -1474,17 +1472,34 @@ void QCocoaWindow::requestUpdate() } } +bool QCocoaWindow::updatesWithDisplayLink() const +{ + // Update via CVDisplayLink if Vsync is enabled + return format().swapInterval() != 0; +} + void QCocoaWindow::deliverUpdateRequest() { + // Don't send update requests for views that need display, as the update + // request doesn't carry any information about dirty rects, so the app + // may end up painting a smaller region than required. (For some reason + // the layer and view's needsDisplay status isn't always in sync, even if + // the view is layer-backed, not layer-hosted, so we check both). + if (m_view.layer.needsDisplay || m_view.needsDisplay) { + qCDebug(lcQpaDrawing) << "View needs display, deferring update request for" << window(); + requestUpdate(); + return; + } + qCDebug(lcQpaDrawing) << "Delivering update request to" << window(); QPlatformWindow::deliverUpdateRequest(); } void QCocoaWindow::requestActivateWindow() { - NSWindow *window = [m_view window]; - [window makeFirstResponder:m_view]; - [window makeKeyWindow]; + QMacAutoReleasePool pool; + [m_view.window makeFirstResponder:m_view]; + [m_view.window makeKeyWindow]; } QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) @@ -1530,7 +1545,8 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) // Deferring window creation breaks OpenGL (the GL context is // set up before the window is shown and needs a proper window) backing:NSBackingStoreBuffered defer:NO - screen:cocoaScreen->nativeScreen()]; + screen:cocoaScreen->nativeScreen() + platformWindow:this]; Q_ASSERT_X(nsWindow.screen == cocoaScreen->nativeScreen(), "QCocoaWindow", "Resulting NSScreen should match the requested NSScreen"); @@ -1540,7 +1556,9 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) QWindowSystemInterface::SynchronousDelivery>(window(), targetScreen); } - nsWindow.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this]; + static QSharedPointer<QNSWindowDelegate> sharedDelegate([[QNSWindowDelegate alloc] init], + [](QNSWindowDelegate *delegate) { [delegate release]; }); + nsWindow.delegate = sharedDelegate.get(); // Prevent Cocoa from releasing the window on close. Qt // handles the close event asynchronously and we want to @@ -1720,6 +1738,14 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window) [window setStyleMask:[window styleMask] | NSWindowStyleMaskTexturedBackground]; window.titlebarAppearsTransparent = YES; + // Setting titlebarAppearsTransparent to YES means that the border thickness has to account + // for the title bar height as well, otherwise sheets will not be presented at the correct + // position, which should be (title bar height + top content border size). + const NSRect frameRect = window.frame; + const NSRect contentRect = [window contentRectForFrameRect:frameRect]; + const CGFloat titlebarHeight = frameRect.size.height - contentRect.size.height; + effectiveTopContentBorderThickness += titlebarHeight; + [window setContentBorderThickness:effectiveTopContentBorderThickness forEdge:NSMaxYEdge]; [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge]; diff --git a/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h new file mode 100644 index 0000000000..872773cb7a --- /dev/null +++ b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#ifndef QIOSURFACEGRAPHICSBUFFER_H +#define QIOSURFACEGRAPHICSBUFFER_H + +#include <qpa/qplatformgraphicsbuffer.h> +#include <private/qcore_mac_p.h> + +QT_BEGIN_NAMESPACE + +class QIOSurfaceGraphicsBuffer : public QPlatformGraphicsBuffer +{ +public: + QIOSurfaceGraphicsBuffer(const QSize &size, const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace); + ~QIOSurfaceGraphicsBuffer(); + + const uchar *data() const override; + uchar *data() override; + int bytesPerLine() const override; + + IOSurfaceRef surface(); + bool isInUse() const; + +protected: + bool doLock(AccessTypes access, const QRect &rect) override; + void doUnlock() override; + +private: + QCFType<IOSurfaceRef> m_surface; + + friend QDebug operator<<(QDebug, const QIOSurfaceGraphicsBuffer *); +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug, const QIOSurfaceGraphicsBuffer *); +#endif + +QT_END_NAMESPACE + +#endif // QIOSURFACEGRAPHICSBUFFER_H diff --git a/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm new file mode 100644 index 0000000000..a367487e85 --- /dev/null +++ b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#include "qiosurfacegraphicsbuffer.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qloggingcategory.h> + +#include <CoreGraphics/CoreGraphics.h> +#include <IOSurface/IOSurface.h> + +// CGColorSpaceCopyPropertyList is available on 10.12 and above, +// but was only added in the 10.14 SDK, so declare it just in case. +extern "C" CFPropertyListRef CGColorSpaceCopyPropertyList(CGColorSpaceRef space); + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcQpaIOSurface, "qt.qpa.backingstore.iosurface"); + +QIOSurfaceGraphicsBuffer::QIOSurfaceGraphicsBuffer(const QSize &size, const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace) + : QPlatformGraphicsBuffer(size, format) +{ + const size_t width = size.width(); + const size_t height = size.height(); + + Q_ASSERT(width <= IOSurfaceGetPropertyMaximum(kIOSurfaceWidth)); + Q_ASSERT(height <= IOSurfaceGetPropertyMaximum(kIOSurfaceHeight)); + + static const char bytesPerElement = 4; + + const size_t bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, width * bytesPerElement); + const size_t totalBytes = IOSurfaceAlignProperty(kIOSurfaceAllocSize, height * bytesPerRow); + + NSDictionary *options = @{ + (id)kIOSurfaceWidth: @(width), + (id)kIOSurfaceHeight: @(height), + (id)kIOSurfacePixelFormat: @(unsigned('BGRA')), + (id)kIOSurfaceBytesPerElement: @(bytesPerElement), + (id)kIOSurfaceBytesPerRow: @(bytesPerRow), + (id)kIOSurfaceAllocSize: @(totalBytes), + }; + + m_surface = IOSurfaceCreate((CFDictionaryRef)options); + Q_ASSERT(m_surface); + + Q_ASSERT(size_t(bytesPerLine()) == bytesPerRow); + Q_ASSERT(size_t(byteCount()) == totalBytes); + + if (colorSpace) { + IOSurfaceSetValue(m_surface, CFSTR("IOSurfaceColorSpace"), + QCFType<CFPropertyListRef>(CGColorSpaceCopyPropertyList(colorSpace))); + } +} + +QIOSurfaceGraphicsBuffer::~QIOSurfaceGraphicsBuffer() +{ +} + +const uchar *QIOSurfaceGraphicsBuffer::data() const +{ + return (const uchar *)IOSurfaceGetBaseAddress(m_surface); +} + +uchar *QIOSurfaceGraphicsBuffer::data() +{ + return (uchar *)IOSurfaceGetBaseAddress(m_surface); +} + +int QIOSurfaceGraphicsBuffer::bytesPerLine() const +{ + return IOSurfaceGetBytesPerRow(m_surface); +} + +IOSurfaceRef QIOSurfaceGraphicsBuffer::surface() +{ + return m_surface; +} + +bool QIOSurfaceGraphicsBuffer::isInUse() const +{ + return IOSurfaceIsInUse(m_surface); +} + +IOSurfaceLockOptions lockOptionsForAccess(QPlatformGraphicsBuffer::AccessTypes access) +{ + IOSurfaceLockOptions lockOptions = 0; + if (!(access & QPlatformGraphicsBuffer::SWWriteAccess)) + lockOptions |= kIOSurfaceLockReadOnly; + return lockOptions; +} + +bool QIOSurfaceGraphicsBuffer::doLock(AccessTypes access, const QRect &rect) +{ + Q_UNUSED(rect); + Q_ASSERT(!isLocked()); + + qCDebug(lcQpaIOSurface) << "Locking" << this << "for" << access; + + // FIXME: Teach QPlatformBackingStore::composeAndFlush about non-2D texture + // targets, so that we can use CGLTexImageIOSurface2D to support TextureAccess. + if (access & (TextureAccess | HWCompositor)) + return false; + + auto lockOptions = lockOptionsForAccess(access); + + // Try without read-back first + lockOptions |= kIOSurfaceLockAvoidSync; + kern_return_t ret = IOSurfaceLock(m_surface, lockOptions, nullptr); + if (ret == kIOSurfaceSuccess) + return true; + + if (ret == kIOReturnCannotLock) { + qCWarning(lcQpaIOSurface) << "Locking of" << this << "requires read-back"; + lockOptions ^= kIOSurfaceLockAvoidSync; + ret = IOSurfaceLock(m_surface, lockOptions, nullptr); + } + + if (ret != kIOSurfaceSuccess) { + qCWarning(lcQpaIOSurface) << "Failed to lock" << this << ret; + return false; + } + + return true; +} + +void QIOSurfaceGraphicsBuffer::doUnlock() +{ + qCDebug(lcQpaIOSurface) << "Unlocking" << this << "from" << isLocked(); + + auto lockOptions = lockOptionsForAccess(isLocked()); + bool success = IOSurfaceUnlock(m_surface, lockOptions, nullptr) == kIOSurfaceSuccess; + Q_ASSERT_X(success, "QIOSurfaceGraphicsBuffer", "Unlocking surface should succeed"); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QIOSurfaceGraphicsBuffer *graphicsBuffer) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug << "QIOSurfaceGraphicsBuffer(" << (const void *)graphicsBuffer; + if (graphicsBuffer) { + debug << ", surface=" << graphicsBuffer->m_surface; + debug << ", size=" << graphicsBuffer->size(); + debug << ", isLocked=" << bool(graphicsBuffer->isLocked()); + debug << ", isInUse=" << graphicsBuffer->isInUse(); + } + debug << ')'; + return debug; +} +#endif // !QT_NO_DEBUG_STREAM + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qmacclipboard.mm b/src/plugins/platforms/cocoa/qmacclipboard.mm index 5939003c64..ba6cfca219 100644 --- a/src/plugins/platforms/cocoa/qmacclipboard.mm +++ b/src/plugins/platforms/cocoa/qmacclipboard.mm @@ -49,6 +49,7 @@ #include <stdlib.h> #include <string.h> #include "qcocoahelpers.h" +#include <type_traits> QT_BEGIN_NAMESPACE @@ -61,6 +62,23 @@ QT_BEGIN_NAMESPACE QMacPasteboard code *****************************************************************************/ +namespace +{ +OSStatus PasteboardGetItemCountSafe(PasteboardRef paste, ItemCount *cnt) +{ + Q_ASSERT(paste); + Q_ASSERT(cnt); + const OSStatus result = PasteboardGetItemCount(paste, cnt); + // Despite being declared unsigned, this API can return -1 + if (std::make_signed<ItemCount>::type(*cnt) < 0) + *cnt = 0; + return result; +} +} // namespace + +// Ensure we don't call the broken one later on +#define PasteboardGetItemCount + class QMacMimeData : public QMimeData { public: @@ -210,7 +228,7 @@ QMacPasteboard::hasOSType(int c_flavor) const sync(); ItemCount cnt = 0; - if (PasteboardGetItemCount(paste, &cnt) || !cnt) + if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt) return false; #ifdef DEBUG_PASTEBOARD @@ -257,7 +275,7 @@ QMacPasteboard::hasFlavor(QString c_flavor) const sync(); ItemCount cnt = 0; - if (PasteboardGetItemCount(paste, &cnt) || !cnt) + if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt) return false; #ifdef DEBUG_PASTEBOARD @@ -374,7 +392,7 @@ QMacPasteboard::formats() const QStringList ret; ItemCount cnt = 0; - if (PasteboardGetItemCount(paste, &cnt) || !cnt) + if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt) return ret; #ifdef DEBUG_PASTEBOARD @@ -417,7 +435,7 @@ QMacPasteboard::hasFormat(const QString &format) const sync(); ItemCount cnt = 0; - if (PasteboardGetItemCount(paste, &cnt) || !cnt) + if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt) return false; #ifdef DEBUG_PASTEBOARD @@ -460,7 +478,7 @@ QMacPasteboard::retrieveData(const QString &format, QVariant::Type) const sync(); ItemCount cnt = 0; - if (PasteboardGetItemCount(paste, &cnt) || !cnt) + if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt) return QByteArray(); #ifdef DEBUG_PASTEBOARD diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 9bd53ed334..5309449dce 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -68,10 +68,12 @@ // Private interface @interface QT_MANGLE_NAMESPACE(QNSView) () - (BOOL)isTransparentForUserInput; +@property (assign) NSView* previousSuperview; +@property (assign) NSWindow* previousWindow; @end @interface QT_MANGLE_NAMESPACE(QNSView) (Drawing) <CALayerDelegate> -- (BOOL)wantsLayerHelper; +- (void)initDrawing; @end @interface QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) : NSObject @@ -83,6 +85,7 @@ @end @interface QT_MANGLE_NAMESPACE(QNSView) (Mouse) +- (void)initMouse; - (NSPoint)screenMousePoint:(NSEvent *)theEvent; - (void)mouseMovedImpl:(NSEvent *)theEvent; - (void)mouseEnteredImpl:(NSEvent *)theEvent; @@ -112,7 +115,6 @@ @implementation QT_MANGLE_NAMESPACE(QNSView) { QPointer<QCocoaWindow> m_platformWindow; - NSTrackingArea *m_trackingArea; Qt::MouseButtons m_buttons; Qt::MouseButtons m_acceptedMouseDowns; Qt::MouseButtons m_frameStrutButtons; @@ -135,36 +137,19 @@ { if ((self = [super initWithFrame:NSZeroRect])) { m_platformWindow = platformWindow; - m_buttons = Qt::NoButton; - m_acceptedMouseDowns = Qt::NoButton; - m_frameStrutButtons = Qt::NoButton; m_sendKeyEvent = false; - m_sendUpAsRightButton = false; m_inputSource = nil; - m_mouseMoveHelper = [[QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) alloc] initWithView:self]; m_resendKeyEvent = false; - m_scrolling = false; m_updatingDrag = false; m_currentlyInterpretedKeyEvent = nil; - m_dontOverrideCtrlLMB = qt_mac_resolveOption(false, platformWindow->window(), - "_q_platform_MacDontOverrideCtrlLMB", "QT_MAC_DONT_OVERRIDE_CTRL_LMB"); - m_trackingArea = nil; self.focusRingType = NSFocusRingTypeNone; - self.cursor = nil; - self.wantsLayer = [self wantsLayerHelper]; - - // Enable high-DPI OpenGL for retina displays. Enabling has the side - // effect that Cocoa will start calling glViewport(0, 0, width, height), - // overriding any glViewport calls in application code. This is usually not a - // problem, except if the application wants to have a "custom" viewport. - // (like the hellogl example) - if (m_platformWindow->window()->supportsOpenGL()) { - self.wantsBestResolutionOpenGLSurface = qt_mac_resolveOption(YES, m_platformWindow->window(), - "_q_mac_wantsBestResolutionOpenGLSurface", "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); - // See also QCocoaGLContext::makeCurrent for software renderer workarounds. - } + self.previousSuperview = nil; + self.previousWindow = nil; + + [self initDrawing]; + [self initMouse]; [self registerDragTypes]; [[NSNotificationCenter defaultCenter] addObserver:self @@ -177,10 +162,8 @@ - (void)dealloc { - if (m_trackingArea) { - [self removeTrackingArea:m_trackingArea]; - [m_trackingArea release]; - } + qCDebug(lcQpaWindow) << "Deallocating" << self; + [m_inputSource release]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [m_mouseMoveHelper release]; @@ -204,8 +187,40 @@ return description; } +// ----------------------------- Re-parenting --------------------------------- + +- (void)removeFromSuperview +{ + QMacAutoReleasePool pool; + [super removeFromSuperview]; +} + +- (void)viewWillMoveToSuperview:(NSView *)newSuperview +{ + Q_ASSERT(!self.previousSuperview); + self.previousSuperview = self.superview; + + if (newSuperview == self.superview) + qCDebug(lcQpaWindow) << "Re-ordering" << self << "inside" << self.superview; + else + qCDebug(lcQpaWindow) << "Re-parenting" << self << "from" << self.superview << "to" << newSuperview; +} + - (void)viewDidMoveToSuperview { + auto cleanup = qScopeGuard([&] { self.previousSuperview = nil; }); + + if (self.superview == self.previousSuperview) { + qCDebug(lcQpaWindow) << "Done re-ordering" << self << "new index:" + << [self.superview.subviews indexOfObject:self]; + return; + } + + qCDebug(lcQpaWindow) << "Done re-parenting" << self << "into" << self.superview; + + // Note: at this point the view's window property hasn't been updated to match the window + // of the new superview. We have to wait for viewDidMoveToWindow for that to be reflected. + if (!m_platformWindow) return; @@ -219,6 +234,36 @@ } } +- (void)viewWillMoveToWindow:(NSWindow *)newWindow +{ + Q_ASSERT(!self.previousWindow); + self.previousWindow = self.window; + + // This callback is documented to be called also when a view is just moved between + // subviews in the same NSWindow, so we're not necessarily moving between NSWindows. + if (newWindow == self.window) + return; + + qCDebug(lcQpaWindow) << "Moving" << self << "from" << self.window << "to" << newWindow; + + // Note: at this point the superview has already been updated, so we know which view inside + // the new window the view will be a child of. +} + +- (void)viewDidMoveToWindow +{ + auto cleanup = qScopeGuard([&] { self.previousWindow = nil; }); + + // This callback is documented to be called also when a view is just moved between + // subviews in the same NSWindow, so we're not necessarily moving between NSWindows. + if (self.window == self.previousWindow) + return; + + qCDebug(lcQpaWindow) << "Done moving" << self << "to" << self.window; +} + +// ---------------------------------------------------------------------------- + - (QWindow *)topLevelWindow { if (!m_platformWindow) @@ -248,12 +293,6 @@ // viewDidUnhide so no reason to override it here. } -- (void)removeFromSuperview -{ - QMacAutoReleasePool pool; - [super removeFromSuperview]; -} - - (BOOL)isTransparentForUserInput { return m_platformWindow->window() && diff --git a/src/plugins/platforms/cocoa/qnsview_complextext.mm b/src/plugins/platforms/cocoa/qnsview_complextext.mm index d357082d33..6ff9b26ca4 100644 --- a/src/plugins/platforms/cocoa/qnsview_complextext.mm +++ b/src/plugins/platforms/cocoa/qnsview_complextext.mm @@ -307,8 +307,8 @@ { Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification) if (([NSApp keyWindow] == self.window) && self.window.firstResponder == self) { - QCocoaInputContext *ic = qobject_cast<QCocoaInputContext *>(QCocoaIntegration::instance()->inputContext()); - ic->updateLocale(); + if (QCocoaInputContext *ic = qobject_cast<QCocoaInputContext *>(QCocoaIntegration::instance()->inputContext())) + ic->updateLocale(); } } diff --git a/src/plugins/platforms/cocoa/qnsview_dragging.mm b/src/plugins/platforms/cocoa/qnsview_dragging.mm index 1c38c5326c..002cb3279e 100644 --- a/src/plugins/platforms/cocoa/qnsview_dragging.mm +++ b/src/plugins/platforms/cocoa/qnsview_dragging.mm @@ -57,9 +57,9 @@ NSFilesPromisePboardType, NSInkTextPboardType, NSMultipleTextSelectionPboardType, mimeTypeGeneric]]; - // Add custom types supported by the application. + // Add custom types supported by the application for (const QString &customType : qt_mac_enabledDraggedTypes()) - [supportedTypes addObject:customType.toNSString()]; + [supportedTypes addObject:customType.toNSString()]; [self registerForDraggedTypes:supportedTypes]; } @@ -79,11 +79,11 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin return target->mapFromGlobal(source->mapToGlobal(point)); } -- (NSDragOperation)draggingSession:(NSDraggingSession *)session - sourceOperationMaskForDraggingContext:(NSDraggingContext)context +- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context { Q_UNUSED(session); Q_UNUSED(context); + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); return qt_mac_mapDropActions(nativeDrag->currentDrag()->supportedActions()); } @@ -134,30 +134,29 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin if (pixmapCursor.isNull()) { switch (response.acceptedAction()) { - case Qt::CopyAction: - nativeCursor = [NSCursor dragCopyCursor]; - break; - case Qt::LinkAction: - nativeCursor = [NSCursor dragLinkCursor]; - break; - case Qt::IgnoreAction: - // Uncomment the next lines if forbiden cursor wanted on non droppable targets. - /*nativeCursor = [NSCursor operationNotAllowedCursor]; - break;*/ - case Qt::MoveAction: - default: - nativeCursor = [NSCursor arrowCursor]; - break; + case Qt::CopyAction: + nativeCursor = [NSCursor dragCopyCursor]; + break; + case Qt::LinkAction: + nativeCursor = [NSCursor dragLinkCursor]; + break; + case Qt::IgnoreAction: + // Uncomment the next lines if forbidden cursor is wanted on undroppable targets. + /*nativeCursor = [NSCursor operationNotAllowedCursor]; + break;*/ + case Qt::MoveAction: + default: + nativeCursor = [NSCursor arrowCursor]; + break; } - } - else { + } else { NSImage *nsimage = qt_mac_create_nsimage(pixmapCursor); nsimage.size = NSSizeFromCGSize((pixmapCursor.size() / pixmapCursor.devicePixelRatioF()).toCGSize()); nativeCursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSZeroPoint]; [nsimage release]; } - // change the cursor + // Change the cursor [nativeCursor set]; // Make sure the cursor is updated correctly if the mouse does not move and window is under cursor @@ -169,39 +168,33 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin if (m_updatingDrag) return; - const QPoint mousePos(QCursor::pos()); - CGEventRef moveEvent(CGEventCreateMouseEvent( - NULL, kCGEventMouseMoved, - CGPointMake(mousePos.x(), mousePos.y()), + QCFType<CGEventRef> moveEvent = CGEventCreateMouseEvent( + nullptr, kCGEventMouseMoved, QCursor::pos().toCGPoint(), kCGMouseButtonLeft // ignored - )); + ); CGEventPost(kCGHIDEventTap, moveEvent); - CFRelease(moveEvent); } -- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { - return [self handleDrag : sender]; + return [self handleDrag:(QEvent::DragEnter) sender:sender]; } -- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender +- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { - m_updatingDrag = true; - const NSDragOperation ret([self handleDrag : sender]); - m_updatingDrag = false; - - return ret; + QScopedValueRollback<bool> rollback(m_updatingDrag, true); + return [self handleDrag:(QEvent::DragMove) sender:sender]; } // Sends drag update to Qt, return the action -- (NSDragOperation)handleDrag:(id <NSDraggingInfo>)sender +- (NSDragOperation)handleDrag:(QEvent::Type)dragType sender:(id<NSDraggingInfo>)sender { if (!m_platformWindow) return NSDragOperationNone; - NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; - QPoint qt_windowPoint(windowPoint.x, windowPoint.y); - Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); + QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint(); + + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(sender.draggingSourceOperationMask); QWindow *target = findEventTargetWindow(m_platformWindow->window()); if (!target) @@ -209,7 +202,12 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin const auto modifiers = [QNSView convertKeyModifiers:NSApp.currentEvent.modifierFlags]; const auto buttons = currentlyPressedMouseButtons(); - const auto point = mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint); + const auto point = mapWindowCoordinates(m_platformWindow->window(), target, windowPoint); + + if (dragType == QEvent::DragEnter) + qCDebug(lcQpaMouse) << dragType << self << "at" << windowPoint; + else + qCDebug(lcQpaMouse) << dragType << "at" << windowPoint << "with" << buttons; QPlatformDragQtResponse response(false, Qt::IgnoreAction, QRect()); QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); @@ -219,7 +217,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin point, qtAllowed, buttons, modifiers); [self updateCursorFromDragResponse:response drag:nativeDrag]; } else { - QCocoaDropData mimeData([sender draggingPasteboard]); + QCocoaDropData mimeData(sender.draggingPasteboard); response = QWindowSystemInterface::handleDrag(target, &mimeData, point, qtAllowed, buttons, modifiers); } @@ -227,7 +225,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin return qt_mac_mapDropAction(response.acceptedAction()); } -- (void)draggingExited:(id <NSDraggingInfo>)sender +- (void)draggingExited:(id<NSDraggingInfo>)sender { if (!m_platformWindow) return; @@ -236,17 +234,18 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin if (!target) return; - NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; - QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint(); + + qCDebug(lcQpaMouse) << QEvent::DragLeave << self << "at" << windowPoint; // Send 0 mime data to indicate drag exit QWindowSystemInterface::handleDrag(target, nullptr, - mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), + mapWindowCoordinates(m_platformWindow->window(), target, windowPoint), Qt::IgnoreAction, Qt::NoButton, Qt::NoModifier); } -// called on drop, send the drop to Qt and return if it was accepted. -- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender +// Called on drop, send the drop to Qt and return if it was accepted +- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { if (!m_platformWindow) return false; @@ -255,31 +254,31 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin if (!target) return false; - NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; - QPoint qt_windowPoint(windowPoint.x, windowPoint.y); - Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); + QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint(); + + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(sender.draggingSourceOperationMask); QPlatformDropQtResponse response(false, Qt::IgnoreAction); QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); const auto modifiers = [QNSView convertKeyModifiers:NSApp.currentEvent.modifierFlags]; const auto buttons = currentlyPressedMouseButtons(); - const auto point = mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint); + const auto point = mapWindowCoordinates(m_platformWindow->window(), target, windowPoint); + + qCDebug(lcQpaMouse) << QEvent::Drop << "at" << windowPoint << "with" << buttons; if (nativeDrag->currentDrag()) { // The drag was started from within the application response = QWindowSystemInterface::handleDrop(target, nativeDrag->dragMimeData(), point, qtAllowed, buttons, modifiers); } else { - QCocoaDropData mimeData([sender draggingPasteboard]); + QCocoaDropData mimeData(sender.draggingPasteboard); response = QWindowSystemInterface::handleDrop(target, &mimeData, point, qtAllowed, buttons, modifiers); } return response.isAccepted(); } -- (void)draggingSession:(NSDraggingSession *)session - endedAtPoint:(NSPoint)screenPoint - operation:(NSDragOperation)operation +- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation { Q_UNUSED(session); Q_UNUSED(screenPoint); @@ -295,6 +294,8 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin nativeDrag->setAcceptedAction(qt_mac_mapNSDragOperation(operation)); m_buttons = currentlyPressedMouseButtons(); + + qCDebug(lcQpaMouse) << "Drag session" << session << "ended, with" << m_buttons; } @end diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm index 4f9d17504d..cb1799b039 100644 --- a/src/plugins/platforms/cocoa/qnsview_drawing.mm +++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm @@ -41,6 +41,24 @@ @implementation QT_MANGLE_NAMESPACE(QNSView) (Drawing) +- (void)initDrawing +{ + self.wantsLayer = [self layerExplicitlyRequested] + || [self shouldUseMetalLayer] + || [self layerEnabledByMacOS]; + + // Enable high-DPI OpenGL for retina displays. Enabling has the side + // effect that Cocoa will start calling glViewport(0, 0, width, height), + // overriding any glViewport calls in application code. This is usually not a + // problem, except if the application wants to have a "custom" viewport. + // (like the hellogl example) + if (m_platformWindow->window()->supportsOpenGL()) { + self.wantsBestResolutionOpenGLSurface = qt_mac_resolveOption(YES, m_platformWindow->window(), + "_q_mac_wantsBestResolutionOpenGLSurface", "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); + // See also QCocoaGLContext::makeCurrent for software renderer workarounds. + } +} + - (BOOL)isOpaque { if (!m_platformWindow) @@ -71,23 +89,38 @@ m_platformWindow->handleExposeEvent(exposedRegion); } -- (BOOL)shouldUseMetalLayer +- (BOOL)layerEnabledByMacOS { - // MetalSurface needs a layer, and so does VulkanSurface (via MoltenVK) - QSurface::SurfaceType surfaceType = m_platformWindow->window()->surfaceType(); - return surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface; + // AppKit has its own logic for this, but if we rely on that, our layers are created + // by AppKit at a point where we've already set up other parts of the platform plugin + // based on the presence of layers or not. Once we've rewritten these parts to support + // dynamically picking up layer enablement we can let AppKit do its thing. + return QMacVersion::buildSDK() >= QOperatingSystemVersion::MacOSMojave + && QMacVersion::currentRuntime() >= QOperatingSystemVersion::MacOSMojave; } -- (BOOL)wantsLayerHelper +- (BOOL)layerExplicitlyRequested { - Q_ASSERT(m_platformWindow); + static bool wantsLayer = [&]() { + int wantsLayer = qt_mac_resolveOption(-1, m_platformWindow->window(), + "_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER"); - bool wantsLayer = qt_mac_resolveOption(true, m_platformWindow->window(), - "_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER"); + if (wantsLayer != -1 && [self layerEnabledByMacOS]) { + qCWarning(lcQpaDrawing) << "Layer-backing cannot be explicitly controlled on 10.14 when built against the 10.14 SDK"; + return true; + } - bool layerForSurfaceType = [self shouldUseMetalLayer]; + return wantsLayer == 1; + }(); + + return wantsLayer; +} - return wantsLayer || layerForSurfaceType; +- (BOOL)shouldUseMetalLayer +{ + // MetalSurface needs a layer, and so does VulkanSurface (via MoltenVK) + QSurface::SurfaceType surfaceType = m_platformWindow->window()->surfaceType(); + return surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface; } - (CALayer *)makeBackingLayer @@ -115,6 +148,14 @@ return [super makeBackingLayer]; } +- (void)setLayer:(CALayer *)layer +{ + qCDebug(lcQpaDrawing) << "Making" << self << "layer-backed with" << layer + << "due to being" << ([self layerExplicitlyRequested] ? "explicitly requested" + : [self shouldUseMetalLayer] ? "needed by surface type" : "enabled by macOS"); + [super setLayer:layer]; +} + - (NSViewLayerContentsRedrawPolicy)layerContentsRedrawPolicy { // We need to set this explicitly since the super implementation @@ -122,6 +163,15 @@ return NSViewLayerContentsRedrawDuringViewResize; } +#if 0 // Disabled until we enable lazy backingstore resizing +- (NSViewLayerContentsPlacement)layerContentsPlacement +{ + // Always place the layer at top left without any automatic scaling, + // so that we can re-use larger layers when resizing a window down. + return NSViewLayerContentsPlacementTopLeft; +} +#endif + - (void)updateMetalLayerDrawableSize:(CAMetalLayer *)layer { CGSize drawableSize = layer.bounds.size; diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm index 28db532ddc..ad751279bb 100644 --- a/src/plugins/platforms/cocoa/qnsview_keys.mm +++ b/src/plugins/platforms/cocoa/qnsview_keys.mm @@ -53,7 +53,7 @@ qtMods |= Qt::AltModifier; if (modifierFlags & NSEventModifierFlagCommand) qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier; - if (modifierFlags & NSEventModifierFlagCommand) + if (modifierFlags & NSEventModifierFlagNumericPad) qtMods |= Qt::KeypadModifier; return qtMods; } diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm index 3d6471005d..0ab09b97d3 100644 --- a/src/plugins/platforms/cocoa/qnsview_mouse.mm +++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm @@ -39,6 +39,22 @@ // This file is included from qnsview.mm, and only used to organize the code +/* + The reason for using this helper is to ensure that QNSView doesn't implement + the NSResponder callbacks for mouseEntered, mouseExited, and mouseMoved. + + If it did, we would get mouse events though the responder chain as well, + for example if a subview has a tracking area of its own and calls super + in the handler, which results in forwarding the event though the responder + chain. The same applies if NSWindow.acceptsMouseMovedEvents is YES. + + By having a helper as the target for our tracking areas, we know for sure + that the events we are getting stem from our own tracking areas. + + FIXME: Ideally we wouldn't need this workaround, and would correctly + interact with the responder chain by e.g. calling super if Qt does not + accept the mouse event +*/ @implementation QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) { QNSView *view; } @@ -77,6 +93,7 @@ - (void)resetMouseButtons { + qCDebug(lcQpaMouse) << "Reseting mouse buttons"; m_buttons = Qt::NoButton; m_frameStrutButtons = Qt::NoButton; } @@ -129,12 +146,50 @@ QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); ulong timestamp = [theEvent timestamp] * 1000; + + auto eventType = cocoaEvent2QtMouseEvent(theEvent); + qCInfo(lcQpaMouse) << "Frame-strut" << eventType << "at" << qtWindowPoint << "with" << m_frameStrutButtons << "in" << self.window; QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons); } @end @implementation QT_MANGLE_NAMESPACE(QNSView) (Mouse) +- (void)initMouse +{ + m_buttons = Qt::NoButton; + m_acceptedMouseDowns = Qt::NoButton; + m_frameStrutButtons = Qt::NoButton; + + m_scrolling = false; + self.cursor = nil; + + m_sendUpAsRightButton = false; + m_dontOverrideCtrlLMB = qt_mac_resolveOption(false, m_platformWindow->window(), + "_q_platform_MacDontOverrideCtrlLMB", "QT_MAC_DONT_OVERRIDE_CTRL_LMB"); + + m_mouseMoveHelper = [[QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) alloc] initWithView:self]; + + NSUInteger trackingOptions = NSTrackingActiveInActiveApp + | NSTrackingMouseEnteredAndExited | NSTrackingCursorUpdate; + + // Ideally, NSTrackingMouseMoved should be turned on only if QWidget::mouseTracking + // is enabled, hover is on, or a tool tip is set. Unfortunately, Qt will send "tooltip" + // events on mouse moves, so we need to turn it on in ALL case. That means EVERY QWindow + // gets to pay the cost of mouse moves delivered to it (Apple recommends keeping it OFF + // because there is a performance hit). + trackingOptions |= NSTrackingMouseMoved; + + // Using NSTrackingInVisibleRect means AppKit will automatically synchronize the + // tracking rect with changes in the view's visible area, so leave it undefined. + trackingOptions |= NSTrackingInVisibleRect; + static const NSRect trackingRect = NSZeroRect; + + QMacAutoReleasePool pool; + [self addTrackingArea:[[[NSTrackingArea alloc] initWithRect:trackingRect + options:trackingOptions owner:m_mouseMoveHelper userInfo:nil] autorelease]]; +} + - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { Q_UNUSED(theEvent) @@ -142,6 +197,11 @@ return NO; if ([self isTransparentForUserInput]) return NO; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint: &windowPoint andScreenPoint: &screenPoint]; + if (!qt_window_private(m_platformWindow->window())->allowClickThrough(screenPoint.toPoint())) + return NO; return YES; } @@ -203,6 +263,11 @@ button = Qt::RightButton; const auto eventType = cocoaEvent2QtMouseEvent(theEvent); + if (eventType == QEvent::MouseMove) + qCDebug(lcQpaMouse) << eventType << "at" << qtWindowPoint << "with" << buttons; + else + qCInfo(lcQpaMouse) << eventType << "of" << button << "at" << qtWindowPoint << "with" << buttons; + QWindowSystemInterface::handleMouseEvent(targetView->m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, buttons, button, eventType, modifiers); @@ -406,44 +471,18 @@ [super otherMouseUp:theEvent]; } -- (void)updateTrackingAreas -{ - [super updateTrackingAreas]; - - QMacAutoReleasePool pool; - - // NSTrackingInVisibleRect keeps care of updating once the tracking is set up, so bail out early - if (m_trackingArea && [[self trackingAreas] containsObject:m_trackingArea]) - return; - - // Ideally, we shouldn't have NSTrackingMouseMoved events included below, it should - // only be turned on if mouseTracking, hover is on or a tool tip is set. - // Unfortunately, Qt will send "tooltip" events on mouse moves, so we need to - // turn it on in ALL case. That means EVERY QWindow gets to pay the cost of - // mouse moves delivered to it (Apple recommends keeping it OFF because there - // is a performance hit). So it goes. - NSUInteger trackingOptions = NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp - | NSTrackingInVisibleRect | NSTrackingMouseMoved | NSTrackingCursorUpdate; - [m_trackingArea release]; - m_trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame] - options:trackingOptions - owner:m_mouseMoveHelper - userInfo:nil]; - [self addTrackingArea:m_trackingArea]; -} - - (void)cursorUpdate:(NSEvent *)theEvent { - qCDebug(lcQpaMouse) << "[QNSView cursorUpdate:]" << self.cursor; - // Note: We do not get this callback when moving from a subview that // uses the legacy cursorRect API, so the cursor is reset to the arrow // cursor. See rdar://34183708 - if (self.cursor) + if (self.cursor && self.cursor != NSCursor.currentCursor) { + qCInfo(lcQpaMouse) << "Updating cursor for" << self << "to" << self.cursor; [self.cursor set]; - else + } else { [super cursorUpdate:theEvent]; + } } - (void)mouseMovedImpl:(NSEvent *)theEvent @@ -498,6 +537,8 @@ QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; m_platformWindow->m_enterLeaveTargetWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); + + qCInfo(lcQpaMouse) << QEvent::Enter << self << "at" << windowPoint << "with" << currentlyPressedMouseButtons(); QWindowSystemInterface::handleEnterEvent(m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); } @@ -516,6 +557,7 @@ if (!m_platformWindow->isContentView()) return; + qCInfo(lcQpaMouse) << QEvent::Leave << self; QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow); m_platformWindow->m_enterLeaveTargetWindow = 0; } @@ -614,8 +656,10 @@ // "isInverted": natural OS X scrolling, inverted from the Qt/other platform/Jens perspective. bool isInverted = [theEvent isDirectionInvertedFromDevice]; - qCDebug(lcQpaMouse) << "scroll wheel @ window pos" << qt_windowPoint << "delta px" << pixelDelta - << "angle" << angleDelta << "phase" << phase << (isInverted ? "inverted" : ""); + qCInfo(lcQpaMouse).nospace() << phase << " at " << qt_windowPoint + << " pixelDelta=" << pixelDelta << " angleDelta=" << angleDelta + << (isInverted ? " inverted=true" : ""); + QWindowSystemInterface::handleWheelEvent(m_platformWindow->window(), qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta, m_currentWheelModifiers, phase, source, isInverted); } diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h index 64f1ed0802..5fc48d826f 100644 --- a/src/plugins/platforms/cocoa/qnswindow.h +++ b/src/plugins/platforms/cocoa/qnswindow.h @@ -60,14 +60,10 @@ QT_FORWARD_DECLARE_CLASS(QCocoaWindow) #define QNSWindowProtocol QT_MANGLE_NAMESPACE(QNSWindowProtocol) @protocol QNSWindowProtocol -@optional -- (BOOL)canBecomeKeyWindow; -- (void)sendEvent:(NSEvent*)theEvent; +- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style + backing:(NSBackingStoreType)backingStoreType defer:(BOOL)flag screen:(NSScreen *)screen + platformWindow:(QCocoaWindow*)window; - (void)closeAndRelease; -- (void)dealloc; -- (BOOL)isOpaque; -- (NSColor *)backgroundColor; -- (NSString *)description; @property (nonatomic, readonly) QCocoaWindow *platformWindow; @end diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm index 1b9dd95cbc..52f765eb31 100644 --- a/src/plugins/platforms/cocoa/qnswindow.mm +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -37,6 +37,8 @@ ** ****************************************************************************/ +#if !defined(QNSWINDOW_PROTOCOL_IMPLMENTATION) + #include "qnswindow.h" #include "qcocoawindow.h" #include "qcocoahelpers.h" @@ -89,44 +91,104 @@ static bool isMouseEvent(NSEvent *ev) } @end -#define super USE_qt_objcDynamicSuper_INSTEAD - @implementation QNSWindow +#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1 +#include "qnswindow.mm" +#undef QNSWINDOW_PROTOCOL_IMPLMENTATION -+ (void)load ++ (void)applicationActivationChanged:(NSNotification*)notification { - const Class windowClass = [self class]; - const Class panelClass = [QNSPanel class]; - - unsigned int protocolCount; - Protocol **protocols = class_copyProtocolList(windowClass, &protocolCount); - for (unsigned int i = 0; i < protocolCount; ++i) { - Protocol *protocol = protocols[i]; - - unsigned int methodDescriptionsCount; - objc_method_description *methods = protocol_copyMethodDescriptionList( - protocol, NO, YES, &methodDescriptionsCount); - - for (unsigned int j = 0; j < methodDescriptionsCount; ++j) { - objc_method_description method = methods[j]; - class_addMethod(panelClass, method.name, - class_getMethodImplementation(windowClass, method.name), - method.types); + const id sender = self; + NSEnumerator<NSWindow*> *windowEnumerator = nullptr; + NSApplication *application = [NSApplication sharedApplication]; + + // Unfortunately there's no NSWindowListOrderedBackToFront, + // so we have to manually reverse the order using an array. + NSMutableArray<NSWindow *> *windows = [NSMutableArray<NSWindow *> new]; + [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack + usingBlock:^(NSWindow *window, BOOL *) { + // For some reason AppKit will give us nil-windows, skip those + if (!window) + return; + + [windows addObject:window]; } - free(methods); + ]; + + windowEnumerator = windows.reverseObjectEnumerator; + + for (NSWindow *window in windowEnumerator) { + // We're meddling with normal and floating windows, so leave others alone + if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel)) + continue; + + // Windows that hide automatically will keep their NSFloatingWindowLevel, + // and hence be on top of the window stack. We don't want to affect these + // windows, as otherwise we might end up with key windows being ordered + // behind these auto-hidden windows when activating the application by + // clicking on a new tool window. + if (window.hidesOnDeactivate) + continue; + + if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) { + QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow; + window.level = notification.name == NSApplicationWillResignActiveNotification ? + NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags()); + } + + // The documentation says that "when a window enters a new level, it’s ordered + // in front of all its peers in that level", but that doesn't seem to be the + // case in practice. To keep the order correct after meddling with the window + // levels, we explicitly order each window to the front. Since we are iterating + // the windows in back-to-front order, this is okey. The call also triggers AppKit + // to re-evaluate the level in relation to windows from other applications, + // working around an issue where our tool windows would stay on top of other + // application windows if activation was transferred to another application by + // clicking on it instead of via the application switcher or Dock. Finally, we + // do this re-ordering for all windows (except auto-hiding ones), otherwise we would + // end up triggering a bug in AppKit where the tool windows would disappear behind + // the application window. + [window orderFront:sender]; } +} + +@end + +@implementation QNSPanel +#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1 +#include "qnswindow.mm" +#undef QNSWINDOW_PROTOCOL_IMPLMENTATION +@end + +#else // QNSWINDOW_PROTOCOL_IMPLMENTATION - free(protocols); +// The following content is mixed in to the QNSWindow and QNSPanel classes via includes + +{ + // Member variables + QPointer<QCocoaWindow> m_platformWindow; +} + +- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style + backing:(NSBackingStoreType)backingStoreType defer:(BOOL)defer screen:(NSScreen *)screen + platformWindow:(QCocoaWindow*)window +{ + // Initializing the window will end up in [NSWindow _commonAwake], which calls many + // of the getters below. We need to set up the platform window reference first, so + // we can properly reflect the window's state during initialization. + m_platformWindow = window; + + return [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:defer screen:screen]; } - (QCocoaWindow *)platformWindow { - return qnsview_cast(self.contentView).platformWindow; + return m_platformWindow; } - (NSString *)description { - NSMutableString *description = [NSMutableString stringWithString:qt_objcDynamicSuper()]; + NSMutableString *description = [NSMutableString stringWithString:[super description]]; #ifndef QT_NO_DEBUG_STREAM QString contentViewDescription; @@ -142,16 +204,15 @@ static bool isMouseEvent(NSEvent *ev) - (BOOL)canBecomeKeyWindow { - QCocoaWindow *pw = self.platformWindow; - if (!pw) + if (!m_platformWindow) return NO; - if (pw->shouldRefuseKeyWindowAndFirstResponder()) + if (m_platformWindow->shouldRefuseKeyWindowAndFirstResponder()) return NO; if ([self isKindOfClass:[QNSPanel class]]) { // Only tool or dialog windows should become key: - Qt::WindowType type = pw->window()->type(); + Qt::WindowType type = m_platformWindow->window()->type(); if (type == Qt::Tool || type == Qt::Dialog) return YES; @@ -170,17 +231,26 @@ static bool isMouseEvent(NSEvent *ev) // Windows with a transient parent (such as combobox popup windows) // cannot become the main window: - QCocoaWindow *pw = self.platformWindow; - if (!pw || pw->window()->transientParent()) + if (!m_platformWindow || m_platformWindow->window()->transientParent()) canBecomeMain = NO; return canBecomeMain; } +- (BOOL)worksWhenModal +{ + if (m_platformWindow && [self isKindOfClass:[QNSPanel class]]) { + Qt::WindowType type = m_platformWindow->window()->type(); + if (type == Qt::Popup || type == Qt::Dialog || type == Qt::Tool) + return YES; + } + + return [super worksWhenModal]; +} + - (BOOL)isOpaque { - return self.platformWindow ? - self.platformWindow->isOpaque() : qt_objcDynamicSuper(); + return m_platformWindow ? m_platformWindow->isOpaque() : [super isOpaque]; } /*! @@ -196,7 +266,7 @@ static bool isMouseEvent(NSEvent *ev) - (NSColor *)backgroundColor { return self.styleMask == NSWindowStyleMaskBorderless - ? [NSColor clearColor] : qt_objcDynamicSuper(); + ? [NSColor clearColor] : [super backgroundColor]; } - (void)sendEvent:(NSEvent*)theEvent @@ -208,7 +278,7 @@ static bool isMouseEvent(NSEvent *ev) // e.g. if being retained by other parts of AppKit, or in an auto-release // pool. We guard against this in QNSView as well, as not all callbacks // come via events, but if they do there's no point in propagating them. - if (!self.platformWindow) + if (!m_platformWindow) return; // Prevent deallocation of this NSWindow during event delivery, as we @@ -216,106 +286,38 @@ static bool isMouseEvent(NSEvent *ev) [[self retain] autorelease]; const char *eventType = object_getClassName(theEvent); - if (QWindowSystemInterface::handleNativeEvent(self.platformWindow->window(), + if (QWindowSystemInterface::handleNativeEvent(m_platformWindow->window(), QByteArray::fromRawData(eventType, qstrlen(eventType)), theEvent, nullptr)) { return; } - qt_objcDynamicSuper(theEvent); + [super sendEvent:theEvent]; - if (!self.platformWindow) + if (!m_platformWindow) return; // Platform window went away while processing event - QCocoaWindow *pw = self.platformWindow; - if (pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { + if (m_platformWindow->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { NSPoint loc = [theEvent locationInWindow]; NSRect windowFrame = [self convertRectFromScreen:self.frame]; NSRect contentFrame = self.contentView.frame; if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO)) - [qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent]; + [qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent]; } } - (void)closeAndRelease { - qCDebug(lcQpaWindow) << "closeAndRelease" << self; - - [self.delegate release]; - self.delegate = nil; - + qCDebug(lcQpaWindow) << "Closing and releasing" << self; [self close]; [self release]; } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" - (void)dealloc { - qCDebug(lcQpaWindow) << "dealloc" << self; - qt_objcDynamicSuper(); -} -#pragma clang diagnostic pop - -+ (void)applicationActivationChanged:(NSNotification*)notification -{ - const id sender = self; - NSEnumerator<NSWindow*> *windowEnumerator = nullptr; - NSApplication *application = [NSApplication sharedApplication]; - - // Unfortunately there's no NSWindowListOrderedBackToFront, - // so we have to manually reverse the order using an array. - NSMutableArray<NSWindow *> *windows = [NSMutableArray<NSWindow *> new]; - [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack - usingBlock:^(NSWindow *window, BOOL *) { - // For some reason AppKit will give us nil-windows, skip those - if (!window) - return; - - [windows addObject:window]; - } - ]; - - windowEnumerator = windows.reverseObjectEnumerator; - - for (NSWindow *window in windowEnumerator) { - // We're meddling with normal and floating windows, so leave others alone - if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel)) - continue; - - // Windows that hide automatically will keep their NSFloatingWindowLevel, - // and hence be on top of the window stack. We don't want to affect these - // windows, as otherwise we might end up with key windows being ordered - // behind these auto-hidden windows when activating the application by - // clicking on a new tool window. - if (window.hidesOnDeactivate) - continue; - - if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) { - QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow; - window.level = notification.name == NSApplicationWillResignActiveNotification ? - NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags()); - } + qCDebug(lcQpaWindow) << "Deallocating" << self; + self.delegate = nil; - // The documentation says that "when a window enters a new level, it’s ordered - // in front of all its peers in that level", but that doesn't seem to be the - // case in practice. To keep the order correct after meddling with the window - // levels, we explicitly order each window to the front. Since we are iterating - // the windows in back-to-front order, this is okey. The call also triggers AppKit - // to re-evaluate the level in relation to windows from other applications, - // working around an issue where our tool windows would stay on top of other - // application windows if activation was transferred to another application by - // clicking on it instead of via the application switcher or Dock. Finally, we - // do this re-ordering for all windows (except auto-hiding ones), otherwise we would - // end up triggering a bug in AppKit where the tool windows would disappear behind - // the application window. - [window orderFront:sender]; - } + [super dealloc]; } -@end - -@implementation QNSPanel -// Implementation shared with QNSWindow, see +[QNSWindow load] above -@end - -#undef super +#endif diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.h b/src/plugins/platforms/cocoa/qnswindowdelegate.h index e71afcbb2a..be870deb3a 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.h +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.h @@ -48,9 +48,6 @@ class QCocoaWindow; QT_END_NAMESPACE @interface QT_MANGLE_NAMESPACE(QNSWindowDelegate) : NSObject <NSWindowDelegate> - -- (instancetype)initWithQCocoaWindow:(QT_PREPEND_NAMESPACE(QCocoaWindow) *)cocoaWindow; - @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindowDelegate); diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm index 97309ea990..087cb3651f 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm @@ -49,23 +49,17 @@ static QRegExp whitespaceRegex = QRegExp(QStringLiteral("\\s*")); -@implementation QNSWindowDelegate { - QCocoaWindow *m_cocoaWindow; -} - -- (instancetype)initWithQCocoaWindow:(QCocoaWindow *)cocoaWindow +static QCocoaWindow *toPlatformWindow(NSWindow *window) { - if ((self = [self init])) - m_cocoaWindow = cocoaWindow; - return self; + return qnsview_cast(window.contentView).platformWindow; } -- (BOOL)windowShouldClose:(NSNotification *)notification +@implementation QNSWindowDelegate + +- (BOOL)windowShouldClose:(NSWindow *)window { - Q_UNUSED(notification); - if (m_cocoaWindow) { - return m_cocoaWindow->windowShouldClose(); - } + if (QCocoaWindow *platformWindow = toPlatformWindow(window)) + return platformWindow->windowShouldClose(); return YES; } @@ -79,14 +73,16 @@ static QRegExp whitespaceRegex = QRegExp(QStringLiteral("\\s*")); - (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)proposedFrame { Q_UNUSED(proposedFrame); - Q_ASSERT(window == m_cocoaWindow->nativeWindow()); - const QWindow *w = m_cocoaWindow->window(); + + QCocoaWindow *platformWindow = toPlatformWindow(window); + Q_ASSERT(platformWindow); + const QWindow *w = platformWindow->window(); // maximumSize() refers to the client size, but AppKit expects the full frame size QSizeF maximumSize = w->maximumSize() + QSize(0, w->frameMargins().top()); // The window should never be larger than the current screen geometry - const QRectF screenGeometry = m_cocoaWindow->screen()->geometry(); + const QRectF screenGeometry = platformWindow->screen()->geometry(); maximumSize = maximumSize.boundedTo(screenGeometry.size()); // Use the current frame position for the initial maximized frame, @@ -106,55 +102,29 @@ static QRegExp whitespaceRegex = QRegExp(QStringLiteral("\\s*")); return QCocoaScreen::mapToNative(maximizedFrame); } -#pragma clang diagnostic push -// NSDisableScreenUpdates and NSEnableScreenUpdates are deprecated, but the -// NSAnimationContext API that replaces them doesn't handle the use-case of -// cross-thread screen update synchronization. -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)frameSize -{ - qCDebug(lcQpaWindow) << window << "will resize to" << QSizeF::fromCGSize(frameSize) - << "- disabling screen updates temporarily"; - - // There may be separate threads rendering to CA layers in this window, - // and if any of them do a swap while the resize is still in progress, - // the visual bounds of that layer will be updated before the visual - // bounds of the window frame, resulting in flickering while resizing. - - // To prevent this we disable screen updates for the whole process until - // the resize is complete, which makes the whole thing visually atomic. - NSDisableScreenUpdates(); - - return frameSize; -} - -- (void)windowDidResize:(NSNotification *)notification -{ - NSWindow *window = notification.object; - qCDebug(lcQpaWindow) << window << "was resized - re-enabling screen updates"; - NSEnableScreenUpdates(); -} -#pragma clang diagnostic pop - - (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu { - Q_UNUSED(window); Q_UNUSED(menu); + QCocoaWindow *platformWindow = toPlatformWindow(window); + Q_ASSERT(platformWindow); + // Only pop up document path if the filename is non-empty. We allow whitespace, to // allow faking a window icon by setting the file path to a single space character. - return !whitespaceRegex.exactMatch(m_cocoaWindow->window()->filePath()); + return !whitespaceRegex.exactMatch(platformWindow->window()->filePath()); } - (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard *)pasteboard { - Q_UNUSED(window); Q_UNUSED(event); Q_UNUSED(dragImageLocation); Q_UNUSED(pasteboard); + QCocoaWindow *platformWindow = toPlatformWindow(window); + Q_ASSERT(platformWindow); + // Only allow drag if the filename is non-empty. We allow whitespace, to // allow faking a window icon by setting the file path to a single space. - return !whitespaceRegex.exactMatch(m_cocoaWindow->window()->filePath()); + return !whitespaceRegex.exactMatch(platformWindow->window()->filePath()); } @end diff --git a/src/plugins/platforms/cocoa/qpaintengine_mac.mm b/src/plugins/platforms/cocoa/qpaintengine_mac.mm index 96506c67fa..3677877538 100644 --- a/src/plugins/platforms/cocoa/qpaintengine_mac.mm +++ b/src/plugins/platforms/cocoa/qpaintengine_mac.mm @@ -47,7 +47,6 @@ #include <private/qpaintengine_raster_p.h> #include <qprinter.h> #include <qstack.h> -#include <qtextcodec.h> #include <qwidget.h> #include <qvarlengtharray.h> #include <qdebug.h> @@ -314,7 +313,6 @@ static void qt_mac_draw_pattern(void *info, CGContextRef c) } pat->image = qt_mac_create_imagemask(pm, pm.rect()); CGImageRelease(swatch); - CGContextRelease(pm_ctx); w *= QMACPATTERN_MASK_MULTIPLIER; h *= QMACPATTERN_MASK_MULTIPLIER; #endif @@ -916,7 +914,6 @@ void QCoreGraphicsPaintEngine::drawTextItem(const QPointF &pos, const QTextItem QFontEngine *fe = ti.fontEngine; const bool textAA = ((state->renderHints() & QPainter::TextAntialiasing) - && (fe->fontDef.pointSize > QCoreTextFontEngine::antialiasingThreshold) && !(fe->fontDef.styleStrategy & QFont::NoAntialias)); const bool lineAA = state->renderHints() & QPainter::Antialiasing; if (textAA != lineAA) |