summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa')
-rw-r--r--src/plugins/platforms/cocoa/qcocoaglcontext.h9
-rw-r--r--src/plugins/platforms/cocoa/qcocoaglcontext.mm132
-rw-r--r--src/plugins/platforms/cocoa/qcocoaintegration.mm21
-rw-r--r--src/plugins/platforms/cocoa/qcocoanativeinterface.mm4
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.h8
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm22
-rw-r--r--src/plugins/platforms/cocoa/qnsview_mouse.mm64
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 &paramList)
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)