diff options
Diffstat (limited to 'src/plugins/platforms/cocoa')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaglcontext.h | 9 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaglcontext.mm | 132 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaintegration.mm | 21 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoanativeinterface.mm | 4 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.h | 8 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 22 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview_mouse.mm | 64 |
7 files changed, 138 insertions, 122 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.h b/src/plugins/platforms/cocoa/qcocoaglcontext.h index 3f7966b247..0e5934bc23 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.h +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.h @@ -41,9 +41,6 @@ #define QCOCOAGLCONTEXT_H #include <QtCore/QPointer> -#include <QtCore/qvector.h> -#include <QtCore/private/qcore_mac_p.h> - #include <qpa/qplatformopenglcontext.h> #include <QtGui/QOpenGLContext> #include <QtGui/QWindow> @@ -68,6 +65,8 @@ public: bool isSharing() const override; bool isValid() const override; + void windowWasHidden(); + NSOpenGLContext *nativeContext() const; QFunctionPointer getProcAddress(const char *procName) override; @@ -75,14 +74,14 @@ public: private: static NSOpenGLPixelFormat *pixelFormatForSurfaceFormat(const QSurfaceFormat &format); - bool setDrawable(QPlatformSurface *surface); + bool setActiveWindow(QWindow *window); void updateSurfaceFormat(); NSOpenGLContext *m_context = nil; NSOpenGLContext *m_shareContext = nil; QSurfaceFormat m_format; + QPointer<QWindow> m_currentWindow; bool m_didCheckForSoftwareContext = false; - QVarLengthArray<QMacScopedObserver, 3> m_observers; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index cba9e90a78..4d0fa2e28e 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -41,8 +41,6 @@ #include "qcocoawindow.h" #include "qcocoahelpers.h" #include <qdebug.h> -#include <QtCore/qscopedvaluerollback.h> -#include <QtCore/qatomic.h> #include <QtCore/private/qcore_mac_p.h> #include <QtPlatformHeaders/qcocoanativecontext.h> #include <dlfcn.h> @@ -322,6 +320,9 @@ void QCocoaGLContext::updateSurfaceFormat() QCocoaGLContext::~QCocoaGLContext() { + if (m_currentWindow && m_currentWindow.data()->handle()) + static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); + [m_context release]; } @@ -330,14 +331,6 @@ bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) qCDebug(lcQpaOpenGLContext) << "Making" << m_context << "current" << "in" << QThread::currentThread() << "for" << surface; - // No need to make context current if it already is. This also ensures - // that we only lock the context once, meaning we don't need to keep - // track of how many times we've locked it to undo it in doneCurrent(). - // Note that we're not using QOpenGLContext::currentContext() here, as - // that has already been updated to match context() before this call. - if ([NSOpenGLContext currentContext] == m_context) - return true; - Q_ASSERT(surface->surface()->supportsOpenGL()); if (surface->surface()->surfaceClass() == QSurface::Offscreen) { @@ -345,14 +338,11 @@ bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) return true; } - if (!setDrawable(surface)) + QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); + if (!setActiveWindow(window)) { + qCDebug(lcQpaOpenGLContext) << "Failed to activate window, skipping makeCurrent"; return false; - - // The context may be owned and used by a dedicated render thread, but - // we will get notifications that trigger update() on the main thread, - // so we need to guard against concurrent uses of the context. We hold - // this lock until swapBuffer() or doneCurrent() gets called. - CGLLockContext(m_context.CGLContextObj); + } [m_context makeCurrentContext]; @@ -373,74 +363,43 @@ bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) } } + update(); return true; } -/*! - Sets the drawable object of the NSOpenGLContext, which is the - frame buffer that is the target of OpenGL drawing operations. -*/ -bool QCocoaGLContext::setDrawable(QPlatformSurface *surface) +bool QCocoaGLContext::setActiveWindow(QWindow *window) { - Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window); - NSView *view = static_cast<QCocoaWindow *>(surface)->view(); - - if (view == m_context.view) + if (window == m_currentWindow.data()) return true; - m_observers.clear(); + Q_ASSERT(window->handle()); + QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); + NSView *view = cocoaWindow->view(); if ((m_context.view = view) != view) { - qCInfo(lcQpaOpenGLContext) << "Failed to set" << view << "as drawable for" << m_context; + qCDebug(lcQpaOpenGLContext) << "Associating" << view << "with" << m_context << "failed"; return false; } - qCInfo(lcQpaOpenGLContext) << "Set drawable for" << m_context << "to" << m_context.view; - - auto updateCallback = [&]() { update(); }; + qCDebug(lcQpaOpenGLContext) << m_context << "now associated with" << m_context.view; - if (view.layer) { - m_observers.append(QMacScopedObserver(view, NSViewFrameDidChangeNotification, updateCallback)); - m_observers.append(QMacScopedObserver(view.window, NSWindowDidChangeScreenNotification, updateCallback)); - } else { - m_observers.append(QMacScopedObserver(view, NSViewGlobalFrameDidChangeNotification, updateCallback)); - } + if (m_currentWindow && m_currentWindow.data()->handle()) + static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); - m_observers.append(QMacScopedObserver([NSApplication sharedApplication], - NSApplicationDidChangeScreenParametersNotification, updateCallback)); + m_currentWindow = window; + cocoaWindow->setCurrentContext(this); return true; } -// NSOpenGLContext is not re-entrant, which means that even when using separate -// contexts per thread, per view, and window, calls into the API will still deadlock. -// Note that this is different from the use of CGLLockContext and CGLUnlockContext -// to prevent concurrent access to the _same_ context from two different threads. -// The latter is expected due to NSOpenGLContext not being thread-safe, while the -// former is working around bugs in NSOpenGLContext that make it not re-entrant. -// For more information see https://openradar.appspot.com/37064579 +// NSOpenGLContext is not re-entrant (https://openradar.appspot.com/37064579) static QMutex s_contextMutex; void QCocoaGLContext::update() { - // Updating the context may result in a call to [NSSurface setFrame:], which - // will recurse back here through NSViewGlobalFrameDidChangeNotification. We - // could use a recursive mutex to prevent a deadlock, but since they are slower - // we opt for a manual recursion check. - static QAtomicPointer<void> updatingThread = nullptr; - if (updatingThread == QThread::currentThreadId()) - return; - - // Guard against concurrent access to the context in the case where there - // is a dedicated render thread operating on the context. See makeCurrent(). - CGLLockContext(m_context.CGLContextObj); - QMutexLocker locker(&s_contextMutex); - QScopedValueRollback<QAtomicPointer<void>> rollback(updatingThread, QThread::currentThreadId()); qCInfo(lcQpaOpenGLContext) << "Updating" << m_context << "for" << m_context.view; [m_context update]; - - CGLUnlockContext(m_context.CGLContextObj); } void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) @@ -451,52 +410,37 @@ void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) if (surface->surface()->surfaceClass() == QSurface::Offscreen) return; // Nothing to do - if (!setDrawable(surface)) { - qCWarning(lcQpaOpenGLContext) << "Can't flush" << m_context - << "without" << surface << "as drawable"; + QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); + if (!setActiveWindow(window)) { + qCWarning(lcQpaOpenGLContext) << "Failed to activate window, skipping swapBuffers"; return; } QMutexLocker locker(&s_contextMutex); [m_context flushBuffer]; - - // We're done flushing, and should release the lock we have on the - // context. To ensure that we're not leaving the context current - // without a lock held on it, we need to couple this with actually - // clearing the context. This should not be a performance hit for the - // case where the same context is made current and then cleared, and - // QOpenGLContext::swapBuffers is documented as requiring makeCurrent - // again before beginning a new frame, so the user can't expect the - // context to be current after a call to swapBuffers(). We explicitly - // go via QOpenGLContext for this, instead of calling our platform - // method directly, as that will ensure QOpenGLContext records the - // fact that there is no longer a current context. We then end up - // in QCocoaGLContext::doneCurrent, where we clear the lock. - context()->doneCurrent(); } void QCocoaGLContext::doneCurrent() { - auto currentContext = QOpenGLContext::currentContext(); - if (!currentContext) - return; - - // QOpenGLContext::doneCurrent() clears the current context, but can - // be called on any context, not necessarily the current one. Since - // we rely on unlocking the context lock we must propagate the call - // to the right context. - if (context() != currentContext) { - currentContext->doneCurrent(); - return; - } - - Q_ASSERT([NSOpenGLContext currentContext] == m_context); - qCDebug(lcQpaOpenGLContext) << "Clearing current context" << [NSOpenGLContext currentContext] << "in" << QThread::currentThread(); + if (m_currentWindow && m_currentWindow.data()->handle()) + static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(nullptr); + + m_currentWindow.clear(); + [NSOpenGLContext clearCurrentContext]; - CGLUnlockContext(m_context.CGLContextObj); +} + +void QCocoaGLContext::windowWasHidden() +{ + // If the window is hidden, we need to unset the m_currentWindow + // variable so that succeeding makeCurrent's will not abort prematurely + // because of the optimization in setActiveWindow. + // Doing a full doneCurrent here is not preferable, because the GL context + // might be rendering in a different thread at this time. + m_currentWindow.clear(); } QSurfaceFormat QCocoaGLContext::format() const diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 612290c9bd..0f87109ada 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -72,6 +72,12 @@ #include <IOKit/graphics/IOGraphicsLib.h> +#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) +@interface NSApplication (MojaveForwardDeclarations) +@property (strong) NSAppearance *appearance NS_AVAILABLE_MAC(10_14); +@end +#endif + static void initResources() { Q_INIT_RESOURCE(qcocoaresources); @@ -133,6 +139,21 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) NSApplication *cocoaApplication = [QNSApplication sharedApplication]; qt_redirectNSApplicationSendEvent(); + if (__builtin_available(macOS 10.14, *)) { + // Disable dark appearance, unless the Info.plist or environment requests that it should be enabled + bool plistEnablesDarkAppearance = [[[NSBundle mainBundle] objectForInfoDictionaryKey: + @"NSRequiresAquaSystemAppearance"] boolValue]; + + bool hasEnvironmentRequiresAquaAppearance; + int environmentRequiresAquaAppearance = qEnvironmentVariableIntValue( + "QT_MAC_REQUIRES_AQUA_SYSTEM_APPEARANCE", &hasEnvironmentRequiresAquaAppearance); + bool environmentEnablesDarkAppearance = hasEnvironmentRequiresAquaAppearance + && environmentRequiresAquaAppearance == 0; + + if (!(plistEnablesDarkAppearance || environmentEnablesDarkAppearance)) + NSApp.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; + } + if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) { // Applications launched from plain executables (without an app // bundle) are "background" applications that does not take keybaord diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm index 7979e430ac..228df50d86 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm @@ -102,6 +102,10 @@ void *QCocoaNativeInterface::nativeResourceForWindow(const QByteArray &resourceS if (resourceString == "nsview") { return static_cast<QCocoaWindow *>(window->handle())->m_view; +#ifndef QT_NO_OPENGL + } else if (resourceString == "nsopenglcontext") { + return static_cast<QCocoaWindow *>(window->handle())->currentContext()->nativeContext(); +#endif } else if (resourceString == "nswindow") { return static_cast<QCocoaWindow *>(window->handle())->nativeWindow(); #if QT_CONFIG(vulkan) diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index 8f1bdb8af0..225c7eda84 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -169,6 +169,11 @@ public: NSUInteger windowStyleMask(Qt::WindowFlags flags); void setWindowZoomButton(Qt::WindowFlags flags); +#ifndef QT_NO_OPENGL + void setCurrentContext(QCocoaGLContext *context); + QCocoaGLContext *currentContext() const; +#endif + bool setWindowModified(bool modified) override; void setFrameStrutEventsEnabled(bool enabled) override; @@ -248,6 +253,9 @@ public: // for QNSView bool m_inSetVisible; bool m_inSetGeometry; bool m_inSetStyleMask; +#ifndef QT_NO_OPENGL + QCocoaGLContext *m_glContext; +#endif QCocoaMenuBar *m_menubar; bool m_needsInvalidateShadow; diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index b79804fd0b..3148501006 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -41,6 +41,9 @@ #include "qcocoascreen.h" #include "qnswindowdelegate.h" #include "qcocoaeventdispatcher.h" +#ifndef QT_NO_OPENGL +#include "qcocoaglcontext.h" +#endif #include "qcocoahelpers.h" #include "qcocoanativeinterface.h" #include "qnsview.h" @@ -148,6 +151,9 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) , m_inSetVisible(false) , m_inSetGeometry(false) , m_inSetStyleMask(false) +#ifndef QT_NO_OPENGL + , m_glContext(nullptr) +#endif , m_menubar(nullptr) , m_needsInvalidateShadow(false) , m_hasModalSession(false) @@ -397,6 +403,10 @@ void QCocoaWindow::setVisible(bool visible) [m_view setHidden:NO]; } else { // qDebug() << "close" << this; +#ifndef QT_NO_OPENGL + if (m_glContext) + m_glContext->windowWasHidden(); +#endif QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = nullptr; if (cocoaEventDispatcher) @@ -1324,6 +1334,18 @@ bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const return ((type & Qt::Popup) == Qt::Popup); } +#ifndef QT_NO_OPENGL +void QCocoaWindow::setCurrentContext(QCocoaGLContext *context) +{ + m_glContext = context; +} + +QCocoaGLContext *QCocoaWindow::currentContext() const +{ + return m_glContext; +} +#endif + /*! Checks if the window is the content view of its immediate NSWindow. diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm index 1de256825a..65bc9f837d 100644 --- a/src/plugins/platforms/cocoa/qnsview_mouse.mm +++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm @@ -566,6 +566,42 @@ NSTimeInterval timestamp = [theEvent timestamp]; ulong qt_timestamp = timestamp * 1000; + Qt::ScrollPhase phase = Qt::NoScrollPhase; + if (theEvent.phase == NSEventPhaseMayBegin || theEvent.phase == NSEventPhaseBegan) { + // MayBegin is likely to happen. We treat it the same as an actual begin, + // and follow it with an update when the actual begin is delivered. + phase = m_scrolling ? Qt::ScrollUpdate : Qt::ScrollBegin; + m_scrolling = true; + } else if (theEvent.phase == NSEventPhaseStationary || theEvent.phase == NSEventPhaseChanged) { + phase = Qt::ScrollUpdate; + } else if (theEvent.phase == NSEventPhaseEnded) { + // A scroll event phase may be followed by a momentum phase after the user releases + // the finger, and in that case we don't want to send a Qt::ScrollEnd until after + // the momentum phase has ended. Unfortunately there isn't any guaranteed way of + // knowing whether or not a NSEventPhaseEnded will be followed by a momentum phase. + // The best we can do is to look at the event queue and hope that the system has + // had time to emit a momentum phase event. + if ([NSApp nextEventMatchingMask:NSScrollWheelMask untilDate:[NSDate distantPast] + inMode:@"QtMomementumEventSearchMode" dequeue:NO].momentumPhase == NSEventPhaseBegan) { + Q_ASSERT(pixelDelta.isNull() && angleDelta.isNull()); + return; // Ignore this event, as it has a delta of 0,0 + } + phase = Qt::ScrollEnd; + m_scrolling = false; + } else if (theEvent.momentumPhase == NSEventPhaseBegan) { + Q_ASSERT(!pixelDelta.isNull() && !angleDelta.isNull()); + phase = Qt::ScrollUpdate; // Send as update, it has a delta + } else if (theEvent.momentumPhase == NSEventPhaseChanged) { + phase = Qt::ScrollMomentum; + } else if (theEvent.phase == NSEventPhaseCancelled + || theEvent.momentumPhase == NSEventPhaseEnded + || theEvent.momentumPhase == NSEventPhaseCancelled) { + phase = Qt::ScrollEnd; + m_scrolling = false; + } else { + Q_ASSERT(theEvent.momentumPhase != NSEventPhaseStationary); + } + // Prevent keyboard modifier state from changing during scroll event streams. // A two-finger trackpad flick generates a stream of scroll events. We want // the keyboard modifier state to be the state at the beginning of the @@ -573,34 +609,16 @@ // mid-stream. One example of this happening would be when pressing cmd // after scrolling in Qt Creator: not taking the phase into account causes // the end of the event stream to be interpreted as font size changes. - NSEventPhase momentumPhase = [theEvent momentumPhase]; - if (momentumPhase == NSEventPhaseNone) + if (theEvent.momentumPhase == NSEventPhaseNone) m_currentWheelModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; - NSEventPhase phase = [theEvent phase]; - Qt::ScrollPhase ph = Qt::ScrollUpdate; - - // MayBegin is likely to happen. We treat it the same as an actual begin. - if (phase == NSEventPhaseMayBegin) { - m_scrolling = true; - ph = Qt::ScrollBegin; - } else if (phase == NSEventPhaseBegan) { - // If MayBegin did not happen, Began is the actual beginning. - if (!m_scrolling) - ph = Qt::ScrollBegin; - m_scrolling = true; - } else if (phase == NSEventPhaseEnded || phase == NSEventPhaseCancelled || - momentumPhase == NSEventPhaseEnded || momentumPhase == NSEventPhaseCancelled) { - ph = Qt::ScrollEnd; - m_scrolling = false; - } else if (phase == NSEventPhaseNone && momentumPhase == NSEventPhaseNone) { - ph = Qt::NoScrollPhase; - } // "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" << ph << (isInverted ? "inverted" : ""); - QWindowSystemInterface::handleWheelEvent(m_platformWindow->window(), qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta, m_currentWheelModifiers, ph, source, isInverted); + qCDebug(lcQpaMouse) << "scroll wheel @ window pos" << qt_windowPoint << "delta px" << pixelDelta + << "angle" << angleDelta << "phase" << phase << (isInverted ? "inverted" : ""); + QWindowSystemInterface::handleWheelEvent(m_platformWindow->window(), qt_timestamp, qt_windowPoint, + qt_screenPoint, pixelDelta, angleDelta, m_currentWheelModifiers, phase, source, isInverted); } #endif // QT_CONFIG(wheelevent) |