summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2017-12-29 15:56:33 +0100
committerLars Knoll <lars.knoll@qt.io>2017-12-30 12:09:53 +0100
commitdb92f2f3aac60218756a1aa8811cf192acc0b0e6 (patch)
treef28a47aebb2f08e221fe7bffafce62a0a96cf7fd /src/plugins/platforms/ios
parentdd61a1d98ea9fbffeaf0e2adcd0ddd58105f6a75 (diff)
parent44da5b863597e761df3545dc7ff02a9b53bbb13d (diff)
Merge remote-tracking branch 'origin/5.9' into 5.10
Conflicts: .qmake.conf mkspecs/win32-g++/qmake.conf src/corelib/global/qglobal_p.h src/corelib/global/qoperatingsystemversion_p.h src/corelib/io/qfilesystemengine_win.cpp src/network/bearer/qbearerengine.cpp src/platformsupport/input/libinput/qlibinputpointer.cpp src/sql/doc/snippets/code/doc_src_sql-driver.cpp src/widgets/kernel/qwidget_p.h src/widgets/kernel/qwidgetwindow.cpp src/widgets/styles/qfusionstyle.cpp tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp Change-Id: I80e2722f481b12fff5d967c28f89208c0e9a1dd8
Diffstat (limited to 'src/plugins/platforms/ios')
-rw-r--r--src/plugins/platforms/ios/qiosapplicationstate.h18
-rw-r--r--src/plugins/platforms/ios/qiosapplicationstate.mm111
-rw-r--r--src/plugins/platforms/ios/qioscontext.h1
-rw-r--r--src/plugins/platforms/ios/qioscontext.mm66
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.mm24
-rw-r--r--src/plugins/platforms/ios/qiosintegration.h3
-rw-r--r--src/plugins/platforms/ios/qiosscreen.h7
-rw-r--r--src/plugins/platforms/ios/qiosscreen.mm71
-rw-r--r--src/plugins/platforms/ios/qiosviewcontroller.mm20
-rw-r--r--src/plugins/platforms/ios/qioswindow.h2
-rw-r--r--src/plugins/platforms/ios/qioswindow.mm9
-rw-r--r--src/plugins/platforms/ios/quiview.h1
-rw-r--r--src/plugins/platforms/ios/quiview.mm72
13 files changed, 312 insertions, 93 deletions
diff --git a/src/plugins/platforms/ios/qiosapplicationstate.h b/src/plugins/platforms/ios/qiosapplicationstate.h
index 1c77b26da1..a68147a72a 100644
--- a/src/plugins/platforms/ios/qiosapplicationstate.h
+++ b/src/plugins/platforms/ios/qiosapplicationstate.h
@@ -40,20 +40,24 @@
#ifndef QIOSAPPLICATIONSTATE_H
#define QIOSAPPLICATIONSTATE_H
-#include <QtCore/qglobal.h>
-#include <QtCore/qvector.h>
+#include <QtCore/qobject.h>
-Q_FORWARD_DECLARE_OBJC_CLASS(NSObject);
+#include <UIKit/UIApplication.h>
QT_BEGIN_NAMESPACE
-class QIOSApplicationState
+class QIOSApplicationState : public QObject
{
+ Q_OBJECT
public:
QIOSApplicationState();
- ~QIOSApplicationState();
-private:
- QVector<NSObject*> m_observers;
+
+ static void handleApplicationStateChanged(UIApplicationState state, const QString &reason);
+ static Qt::ApplicationState toQtApplicationState(UIApplicationState state);
+
+Q_SIGNALS:
+ void applicationStateWillChange(Qt::ApplicationState);
+ void applicationStateDidChange(Qt::ApplicationState);
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosapplicationstate.mm b/src/plugins/platforms/ios/qiosapplicationstate.mm
index 7c8e1f9927..3407aebf8f 100644
--- a/src/plugins/platforms/ios/qiosapplicationstate.mm
+++ b/src/plugins/platforms/ios/qiosapplicationstate.mm
@@ -40,82 +40,77 @@
#include "qiosapplicationstate.h"
#include "qiosglobal.h"
+#include "qiosintegration.h"
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/qcoreapplication.h>
#include <QtGui/private/qguiapplication_p.h>
-#import <UIKit/UIKit.h>
+QT_BEGIN_NAMESPACE
-static Qt::ApplicationState qtApplicationState(UIApplicationState uiApplicationState)
+static void qRegisterApplicationStateNotifications()
{
- switch (uiApplicationState) {
- case UIApplicationStateActive:
- // The application is visible in front, and receiving events
- return Qt::ApplicationActive;
- case UIApplicationStateInactive:
- // The app is running in the foreground but is not receiving events. This
- // typically happens while transitioning to/from active/background, like
- // upon app launch or when receiving incoming calls.
- return Qt::ApplicationInactive;
- case UIApplicationStateBackground:
- // Normally the app would enter this state briefly before it gets
- // suspeded (you have five seconds, according to Apple).
- // You can request more time and start a background task, which would
- // normally map closer to Qt::ApplicationHidden. But since we have no
- // API for doing that yet, we handle this state as "about to be suspended".
- // Note: A screen-shot for the SpringBoard will also be taken after this
- // call returns.
- return Qt::ApplicationSuspended;
+ NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
+ NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
+
+ // Map between notifications and corresponding application state. Note that
+ // there's no separate notification for moving to UIApplicationStateInactive,
+ // so we use UIApplicationWillResignActiveNotification as an intermediate.
+ static QMap<NSNotificationName, UIApplicationState> notifications {
+ { UIApplicationWillEnterForegroundNotification, UIApplicationStateInactive },
+ { UIApplicationDidBecomeActiveNotification, UIApplicationStateActive },
+ { UIApplicationWillResignActiveNotification, UIApplicationStateInactive },
+ { UIApplicationDidEnterBackgroundNotification, UIApplicationStateBackground },
+ };
+
+ for (auto i = notifications.constBegin(); i != notifications.constEnd(); ++i) {
+ [notificationCenter addObserverForName:i.key() object:nil queue:mainQueue
+ usingBlock:^void(NSNotification *notification) {
+ NSRange nameRange = NSMakeRange(2, notification.name.length - 14);
+ QString reason = QString::fromNSString([notification.name substringWithRange:nameRange]);
+ QIOSApplicationState::handleApplicationStateChanged(i.value(), reason);
+ }];
}
-}
-static void handleApplicationStateChanged(UIApplicationState uiApplicationState)
-{
- Qt::ApplicationState state = qtApplicationState(uiApplicationState);
- qCDebug(lcQpaApplication) << "moved to" << state;
- QWindowSystemInterface::handleApplicationStateChanged(state);
+ // Initialize correct startup state, which may not be the Qt default (inactive)
+ UIApplicationState startupState = [UIApplication sharedApplication].applicationState;
+ QIOSApplicationState::handleApplicationStateChanged(startupState, QLatin1String("Application loaded"));
}
-
-QT_BEGIN_NAMESPACE
+Q_CONSTRUCTOR_FUNCTION(qRegisterApplicationStateNotifications)
QIOSApplicationState::QIOSApplicationState()
{
- NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
-
- m_observers.push_back([notificationCenter addObserverForName:UIApplicationDidBecomeActiveNotification
- object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *) {
- handleApplicationStateChanged(UIApplicationStateActive);
- }
- ]);
-
- m_observers.push_back([notificationCenter addObserverForName:UIApplicationWillResignActiveNotification
- object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *) {
- // Note: UIApplication is still UIApplicationStateActive at this point,
- // but since there is no separate notification for the inactive state,
- // we report UIApplicationStateInactive now.
- handleApplicationStateChanged(UIApplicationStateInactive);
- }
- ]);
-
- m_observers.push_back([notificationCenter addObserverForName:UIApplicationDidEnterBackgroundNotification
- object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *) {
- handleApplicationStateChanged(UIApplicationStateBackground);
- }
- ]);
-
- // Initialize correct startup state, which may not be the Qt default (inactive)
UIApplicationState startupState = [UIApplication sharedApplication].applicationState;
- QGuiApplicationPrivate::applicationState = qtApplicationState(startupState);
+ QIOSApplicationState::handleApplicationStateChanged(startupState, QLatin1String("Application launched"));
}
-QIOSApplicationState::~QIOSApplicationState()
+void QIOSApplicationState::handleApplicationStateChanged(UIApplicationState uiState, const QString &reason)
{
- NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
- foreach (const NSObject* observer, m_observers)
- [notificationCenter removeObserver:observer];
+ Qt::ApplicationState state = toQtApplicationState(uiState);
+ qCDebug(lcQpaApplication) << qPrintable(reason)
+ << "- moving from" << QGuiApplication::applicationState() << "to" << state;
+
+ if (QIOSIntegration *integration = QIOSIntegration::instance()) {
+ emit integration->applicationState.applicationStateWillChange(state);
+ QWindowSystemInterface::handleApplicationStateChanged(state);
+ emit integration->applicationState.applicationStateDidChange(state);
+ qCDebug(lcQpaApplication) << "done moving to" << state;
+ } else {
+ qCDebug(lcQpaApplication) << "no platform integration yet, setting state directly";
+ QGuiApplicationPrivate::applicationState = state;
+ }
}
-QT_END_NAMESPACE
+Qt::ApplicationState QIOSApplicationState::toQtApplicationState(UIApplicationState state)
+{
+ switch (state) {
+ case UIApplicationStateActive: return Qt::ApplicationActive;
+ case UIApplicationStateInactive: return Qt::ApplicationInactive;
+ case UIApplicationStateBackground: return Qt::ApplicationSuspended;
+ }
+}
+#include "moc_qiosapplicationstate.cpp"
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qioscontext.h b/src/plugins/platforms/ios/qioscontext.h
index 5b7917f7b4..ce50eff1d9 100644
--- a/src/plugins/platforms/ios/qioscontext.h
+++ b/src/plugins/platforms/ios/qioscontext.h
@@ -87,6 +87,7 @@ private:
bool isComplete;
};
+ static bool verifyGraphicsHardwareAvailability();
static void deleteBuffers(const FramebufferObject &framebufferObject);
FramebufferObject &backingFramebufferObjectFor(QPlatformSurface *) const;
diff --git a/src/plugins/platforms/ios/qioscontext.mm b/src/plugins/platforms/ios/qioscontext.mm
index 6a6cbb4324..03643c19a9 100644
--- a/src/plugins/platforms/ios/qioscontext.mm
+++ b/src/plugins/platforms/ios/qioscontext.mm
@@ -38,10 +38,13 @@
****************************************************************************/
#include "qioscontext.h"
+
+#include "qiosintegration.h"
#include "qioswindow.h"
#include <dlfcn.h>
+#include <QtGui/QGuiApplication>
#include <QtGui/QOpenGLContext>
#import <OpenGLES/EAGL.h>
@@ -136,6 +139,9 @@ bool QIOSContext::makeCurrent(QPlatformSurface *surface)
{
Q_ASSERT_IS_GL_SURFACE(surface);
+ if (!verifyGraphicsHardwareAvailability())
+ return false;
+
[EAGLContext setCurrentContext:m_eaglContext];
// For offscreen surfaces we don't prepare a default FBO
@@ -214,18 +220,12 @@ void QIOSContext::swapBuffers(QPlatformSurface *surface)
{
Q_ASSERT_IS_GL_SURFACE(surface);
+ if (!verifyGraphicsHardwareAvailability())
+ return;
+
if (surface->surface()->surfaceClass() == QSurface::Offscreen)
return; // Nothing to do
- // When using threaded rendering, the render-thread may not have picked up
- // yet on the fact that a window is no longer exposed, and will try to swap
- // a non-exposed window. This may in some cases result in crashes, e.g. when
- // iOS is suspending an application, so we have an extra guard here.
- if (!static_cast<QIOSWindow *>(surface)->isExposed()) {
- qCDebug(lcQpaGLContext, "Detected swapBuffers on a non-exposed window, skipping flush");
- return;
- }
-
FramebufferObject &framebufferObject = backingFramebufferObjectFor(surface);
Q_ASSERT_X(framebufferObject.isComplete, "QIOSContext", "swapBuffers on incomplete FBO");
@@ -287,6 +287,54 @@ bool QIOSContext::needsRenderbufferResize(QPlatformSurface *surface) const
return false;
}
+bool QIOSContext::verifyGraphicsHardwareAvailability()
+{
+ // Per the iOS OpenGL ES Programming Guide, background apps may not execute commands on the
+ // graphics hardware. Specifically: "In your app delegate’s applicationDidEnterBackground:
+ // method, your app may want to delete some of its OpenGL ES objects to make memory and
+ // resources available to the foreground app. Call the glFinish function to ensure that
+ // the resources are removed immediately. After your app exits its applicationDidEnterBackground:
+ // method, it must not make any new OpenGL ES calls. If it makes an OpenGL ES call, it is
+ // terminated by iOS.".
+ static bool applicationBackgrounded = QGuiApplication::applicationState() == Qt::ApplicationSuspended;
+
+ static dispatch_once_t onceToken = 0;
+ dispatch_once(&onceToken, ^{
+ QIOSApplicationState *applicationState = &QIOSIntegration::instance()->applicationState;
+ connect(applicationState, &QIOSApplicationState::applicationStateWillChange, [](Qt::ApplicationState state) {
+ if (applicationBackgrounded && state != Qt::ApplicationSuspended) {
+ qCDebug(lcQpaGLContext) << "app no longer backgrounded, rendering enabled";
+ applicationBackgrounded = false;
+ }
+ });
+ connect(applicationState, &QIOSApplicationState::applicationStateDidChange, [](Qt::ApplicationState state) {
+ if (state != Qt::ApplicationSuspended)
+ return;
+
+ qCDebug(lcQpaGLContext) << "app backgrounded, rendering disabled";
+ applicationBackgrounded = true;
+
+ // By the time we receive this signal the application has moved into
+ // Qt::ApplactionStateSuspended, and all windows have been obscured,
+ // which should stop all rendering. If there's still an active GL context,
+ // we follow Apple's advice and call glFinish before making it inactive.
+ if (QOpenGLContext *currentContext = QOpenGLContext::currentContext()) {
+ qCWarning(lcQpaGLContext) << "explicitly glFinishing and deactivating" << currentContext;
+ glFinish();
+ currentContext->doneCurrent();
+ }
+ });
+ });
+
+ if (applicationBackgrounded) {
+ static const char warning[] = "OpenGL ES calls are not allowed while an application is backgrounded";
+ Q_ASSERT_X(!applicationBackgrounded, "QIOSContext", warning);
+ qCWarning(lcQpaGLContext, warning);
+ }
+
+ return !applicationBackgrounded;
+}
+
void QIOSContext::windowDestroyed(QObject *object)
{
QIOSWindow *window = static_cast<QIOSWindow *>(object);
diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm
index 237077400b..050c592aca 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.mm
+++ b/src/plugins/platforms/ios/qiosinputcontext.mm
@@ -500,23 +500,25 @@ void QIOSInputContext::scrollToCursor()
QWindow *focusWindow = qApp->focusWindow();
QRect cursorRect = qApp->inputMethod()->cursorRectangle().translated(focusWindow->geometry().topLeft()).toRect();
- if (cursorRect.isNull()) {
- scroll(0);
- return;
- }
-
- // Add some padding so that the cusor does not end up directly above the keyboard
- static const int kCursorRectPadding = 20;
- cursorRect.adjust(0, -kCursorRectPadding, 0, kCursorRectPadding);
// We explicitly ask for the geometry of the screen instead of the availableGeometry,
- // as we hide the statusbar when scrolling the screen, so the available geometry will
+ // as we hide the status bar when scrolling the screen, so the available geometry will
// include the space taken by the status bar at the moment.
QRect screenGeometry = focusWindow->screen()->geometry();
+
+ if (!cursorRect.isNull()) {
+ // Add some padding so that the cursor does not end up directly above the keyboard
+ static const int kCursorRectPadding = 20;
+ cursorRect.adjust(0, -kCursorRectPadding, 0, kCursorRectPadding);
+
+ // Make sure the cursor rect is still within the screen geometry after padding
+ cursorRect &= screenGeometry;
+ }
+
QRect keyboardGeometry = QRectF::fromCGRect(m_keyboardState.keyboardEndRect).toRect();
QRect availableGeometry = (QRegion(screenGeometry) - keyboardGeometry).boundingRect();
- if (!availableGeometry.contains(cursorRect, true)) {
+ if (!cursorRect.isNull() && !availableGeometry.contains(cursorRect)) {
qImDebug() << "cursor rect" << cursorRect << "not fully within" << availableGeometry;
int scrollToCenter = -(availableGeometry.center() - cursorRect.center()).y();
int scrollToBottom = focusWindow->screen()->geometry().bottom() - availableGeometry.bottom();
@@ -528,6 +530,8 @@ void QIOSInputContext::scrollToCursor()
void QIOSInputContext::scroll(int y)
{
+ Q_ASSERT(y >= 0);
+
UIView *rootView = scrollableRootView();
if (!rootView)
return;
diff --git a/src/plugins/platforms/ios/qiosintegration.h b/src/plugins/platforms/ios/qiosintegration.h
index 6f3f18af48..522cc032ff 100644
--- a/src/plugins/platforms/ios/qiosintegration.h
+++ b/src/plugins/platforms/ios/qiosintegration.h
@@ -104,6 +104,8 @@ public:
QFactoryLoader *optionalPlugins() { return m_optionalPlugins; }
+ QIOSApplicationState applicationState;
+
private:
QPlatformFontDatabase *m_fontDatabase;
#ifndef Q_OS_TVOS
@@ -111,7 +113,6 @@ private:
#endif
QPlatformInputContext *m_inputContext;
QTouchDevice *m_touchDevice;
- QIOSApplicationState m_applicationState;
QIOSServices *m_platformServices;
mutable QPlatformAccessibility *m_accessibility;
QFactoryLoader *m_optionalPlugins;
diff --git a/src/plugins/platforms/ios/qiosscreen.h b/src/plugins/platforms/ios/qiosscreen.h
index 9fcce42825..be0f301710 100644
--- a/src/plugins/platforms/ios/qiosscreen.h
+++ b/src/plugins/platforms/ios/qiosscreen.h
@@ -46,6 +46,10 @@
@class QIOSOrientationListener;
+@interface QUIWindow : UIWindow
+@property (nonatomic, readonly) BOOL sendingEvent;
+@end
+
QT_BEGIN_NAMESPACE
class QIOSScreen : public QObject, public QPlatformScreen
@@ -56,6 +60,8 @@ public:
QIOSScreen(UIScreen *screen);
~QIOSScreen();
+ QString name() const override;
+
QRect geometry() const Q_DECL_OVERRIDE;
QRect availableGeometry() const Q_DECL_OVERRIDE;
int depth() const Q_DECL_OVERRIDE;
@@ -63,6 +69,7 @@ public:
QSizeF physicalSize() const Q_DECL_OVERRIDE;
QDpi logicalDpi() const Q_DECL_OVERRIDE;
qreal devicePixelRatio() const Q_DECL_OVERRIDE;
+ qreal refreshRate() const override;
Qt::ScreenOrientation nativeOrientation() const Q_DECL_OVERRIDE;
Qt::ScreenOrientation orientation() const Q_DECL_OVERRIDE;
diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm
index 7c88cddd54..c394592d76 100644
--- a/src/plugins/platforms/ios/qiosscreen.mm
+++ b/src/plugins/platforms/ios/qiosscreen.mm
@@ -174,6 +174,41 @@ static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen)
@end
+@interface UIScreen (Compatibility)
+@property (nonatomic, readonly) CGRect qt_applicationFrame;
+@end
+
+@implementation UIScreen (Compatibility)
+- (CGRect)qt_applicationFrame
+{
+#ifdef Q_OS_IOS
+ return self.applicationFrame;
+#else
+ return self.bounds;
+#endif
+}
+@end
+
+// -------------------------------------------------------------------------
+
+@implementation QUIWindow
+
+- (id)initWithFrame:(CGRect)frame
+{
+ if ((self = [super initWithFrame:frame]))
+ self->_sendingEvent = NO;
+
+ return self;
+}
+
+- (void)sendEvent:(UIEvent *)event
+{
+ QScopedValueRollback<BOOL> sendingEvent(self->_sendingEvent, YES);
+ [super sendEvent:event];
+}
+
+@end
+
// -------------------------------------------------------------------------
QT_BEGIN_NAMESPACE
@@ -245,7 +280,7 @@ QIOSScreen::QIOSScreen(UIScreen *screen)
if (!m_uiWindow) {
// Create a window and associated view-controller that we can use
- m_uiWindow = [[UIWindow alloc] initWithFrame:[m_uiScreen bounds]];
+ m_uiWindow = [[QUIWindow alloc] initWithFrame:[m_uiScreen bounds]];
m_uiWindow.rootViewController = [[[QIOSViewController alloc] initWithQIOSScreen:this] autorelease];
}
@@ -264,17 +299,31 @@ QIOSScreen::~QIOSScreen()
[m_uiWindow release];
}
+QString QIOSScreen::name() const
+{
+ if (m_uiScreen == [UIScreen mainScreen]) {
+ return QString::fromNSString([UIDevice currentDevice].model)
+ + QLatin1String(" built-in display");
+ } else {
+ return QLatin1String("External display");
+ }
+}
+
void QIOSScreen::updateProperties()
{
QRect previousGeometry = m_geometry;
QRect previousAvailableGeometry = m_availableGeometry;
m_geometry = QRectF::fromCGRect(m_uiScreen.bounds).toRect();
-#ifdef Q_OS_TVOS
- m_availableGeometry = m_geometry;
-#else
- m_availableGeometry = QRectF::fromCGRect(m_uiScreen.applicationFrame).toRect();
-#endif
+
+ // The application frame doesn't take safe area insets into account, and
+ // the safe area insets are not available before the UIWindow is shown,
+ // and do not take split-view constraints into account, so we have to
+ // combine the two to get the correct available geometry.
+ QRect applicationFrame = QRectF::fromCGRect(m_uiScreen.qt_applicationFrame).toRect();
+ UIEdgeInsets safeAreaInsets = m_uiWindow.qt_safeAreaInsets;
+ m_availableGeometry = m_geometry.adjusted(safeAreaInsets.left, safeAreaInsets.top,
+ -safeAreaInsets.right, -safeAreaInsets.bottom).intersected(applicationFrame);
#ifndef Q_OS_TVOS
if (m_uiScreen == [UIScreen mainScreen]) {
@@ -396,6 +445,16 @@ qreal QIOSScreen::devicePixelRatio() const
return [m_uiScreen scale];
}
+qreal QIOSScreen::refreshRate() const
+{
+#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_NA, 100300, 110000, __WATCHOS_NA)
+ if (__builtin_available(iOS 10.3, tvOS 11, *))
+ return m_uiScreen.maximumFramesPerSecond;
+#endif
+
+ return 60.0;
+}
+
Qt::ScreenOrientation QIOSScreen::nativeOrientation() const
{
CGRect nativeBounds =
diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm
index a7f1254064..a7663b9e94 100644
--- a/src/plugins/platforms/ios/qiosviewcontroller.mm
+++ b/src/plugins/platforms/ios/qiosviewcontroller.mm
@@ -156,6 +156,26 @@
- (void)layoutSubviews
{
+ if (QGuiApplication::applicationState() == Qt::ApplicationSuspended) {
+ // Despite the OpenGL ES Programming Guide telling us to avoid all
+ // use of OpenGL while in the background, iOS will perform its view
+ // snapshotting for the app switcher after the application has been
+ // backgrounded; once for each orientation. Presumably the expectation
+ // is that no rendering needs to be done to provide an alternate
+ // orientation snapshot, just relayouting of views. But in our case,
+ // or any non-stretchable content case such as a OpenGL based game,
+ // this is not true. Instead of continuing layout, which will send
+ // potentially expensive geometry changes (with isExposed false,
+ // since we're in the background), we short-circuit the snapshotting
+ // here. iOS will still use the latest rendered frame to create the
+ // application switcher thumbnail, but it will be based on the last
+ // active orientation of the application.
+ QIOSScreen *screen = self.qtViewController->m_screen;
+ qCDebug(lcQpaWindow) << "ignoring layout of subviews while suspended,"
+ << "likely system snapshot of" << screen->screen()->primaryOrientation();
+ return;
+ }
+
for (int i = int(self.subviews.count) - 1; i >= 0; --i) {
UIView *view = static_cast<UIView *>([self.subviews objectAtIndex:i]);
if (![view isKindOfClass:[QUIView class]])
diff --git a/src/plugins/platforms/ios/qioswindow.h b/src/plugins/platforms/ios/qioswindow.h
index da8a6aabdc..14fa2084c9 100644
--- a/src/plugins/platforms/ios/qioswindow.h
+++ b/src/plugins/platforms/ios/qioswindow.h
@@ -71,6 +71,8 @@ public:
bool isExposed() const Q_DECL_OVERRIDE;
void propagateSizeHints() Q_DECL_OVERRIDE {}
+ QMargins safeAreaMargins() const override;
+
void raise() Q_DECL_OVERRIDE{ raiseOrLower(true); }
void lower() Q_DECL_OVERRIDE { raiseOrLower(false); }
diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm
index bcec9899f7..38136c05db 100644
--- a/src/plugins/platforms/ios/qioswindow.mm
+++ b/src/plugins/platforms/ios/qioswindow.mm
@@ -223,9 +223,16 @@ void QIOSWindow::applyGeometry(const QRect &rect)
[m_view layoutIfNeeded];
}
+QMargins QIOSWindow::safeAreaMargins() const
+{
+ UIEdgeInsets safeAreaInsets = m_view.qt_safeAreaInsets;
+ return QMargins(safeAreaInsets.left, safeAreaInsets.top,
+ safeAreaInsets.right, safeAreaInsets.bottom);
+}
+
bool QIOSWindow::isExposed() const
{
- return qApp->applicationState() >= Qt::ApplicationActive
+ return qApp->applicationState() != Qt::ApplicationSuspended
&& window()->isVisible() && !window()->geometry().isEmpty();
}
diff --git a/src/plugins/platforms/ios/quiview.h b/src/plugins/platforms/ios/quiview.h
index 1500f0b41c..1ce9007a35 100644
--- a/src/plugins/platforms/ios/quiview.h
+++ b/src/plugins/platforms/ios/quiview.h
@@ -77,5 +77,6 @@ QT_END_NAMESPACE
- (QWindow *)qwindow;
- (UIViewController *)viewController;
- (QIOSViewController*)qtViewController;
+@property (nonatomic, readonly) UIEdgeInsets qt_safeAreaInsets;
@end
diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm
index a2ecc8c3cd..42e57d0f4f 100644
--- a/src/plugins/platforms/ios/quiview.mm
+++ b/src/plugins/platforms/ios/quiview.mm
@@ -43,6 +43,7 @@
#include "qiosintegration.h"
#include "qiosviewcontroller.h"
#include "qiostextresponder.h"
+#include "qiosscreen.h"
#include "qioswindow.h"
#ifndef Q_OS_TVOS
#include "qiosmenu.h"
@@ -54,6 +55,24 @@
@implementation QUIView
++ (void)load
+{
+ if (QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::IOS, 11)) {
+ // iOS 11 handles this though [UIView safeAreaInsetsDidChange], but there's no signal for
+ // the corresponding top and bottom layout guides that we use on earlier versions. Note
+ // that we use the _will_ change version of the notification, because we want to react
+ // to the change as early was possible. But since the top and bottom layout guides have
+ // not been updated at this point we use asynchronous delivery of the event, so that the
+ // event is processed by QtGui just after iOS has updated the layout margins.
+ [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillChangeStatusBarFrameNotification
+ object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *) {
+ for (QWindow *window : QGuiApplication::allWindows())
+ QWindowSystemInterface::handleSafeAreaMarginsChanged<QWindowSystemInterface::AsynchronousDelivery>(window);
+ }
+ ];
+ }
+}
+
+ (Class)layerClass
{
return [CAEAGLLayer class];
@@ -98,6 +117,22 @@
self.layer.borderColor = colorWithBrightness(1.0);
self.layer.borderWidth = 1.0;
}
+
+#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_NA, 110000, 110000, __WATCHOS_NA)
+ if (qEnvironmentVariableIsSet("QT_IOS_DEBUG_WINDOW_SAFE_AREAS")) {
+ if (__builtin_available(iOS 11, tvOS 11, *)) {
+ UIView *safeAreaOverlay = [[UIView alloc] initWithFrame:CGRectZero];
+ [safeAreaOverlay setBackgroundColor:[UIColor colorWithRed:0.3 green:0.7 blue:0.9 alpha:0.3]];
+ [self addSubview:safeAreaOverlay];
+
+ safeAreaOverlay.translatesAutoresizingMaskIntoConstraints = NO;
+ [safeAreaOverlay.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES;
+ [safeAreaOverlay.leftAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leftAnchor].active = YES;
+ [safeAreaOverlay.rightAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.rightAnchor].active = YES;
+ [safeAreaOverlay.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES;
+ }
+ }
+#endif
}
return self;
@@ -197,6 +232,11 @@
QWindowSystemInterface::handleExposeEvent(m_qioswindow->window(), region);
}
+- (void)safeAreaInsetsDidChange
+{
+ QWindowSystemInterface::handleSafeAreaMarginsChanged(m_qioswindow->window());
+}
+
// -------------------------------------------------------------------------
- (BOOL)canBecomeFirstResponder
@@ -354,7 +394,21 @@
- (void)sendTouchEventWithTimestamp:(ulong)timeStamp
{
QIOSIntegration *iosIntegration = QIOSIntegration::instance();
- QWindowSystemInterface::handleTouchEvent(m_qioswindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
+ if (!static_cast<QUIWindow *>(self.window).sendingEvent) {
+ // The event is likely delivered as part of delayed touch delivery, via
+ // _UIGestureEnvironmentSortAndSendDelayedTouches, due to one of the two
+ // _UISystemGestureGateGestureRecognizer instances on the top level window
+ // having its delaysTouchesBegan set to YES. During this delivery, it's not
+ // safe to spin up a recursive event loop, as our calling function is not
+ // reentrant, so any gestures used by the recursive code, e.g. a native
+ // alert dialog, will fail to recognize. To be on the safe side, we deliver
+ // the event asynchronously.
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>(
+ m_qioswindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
+ } else {
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
+ m_qioswindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
+ }
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
@@ -540,6 +594,22 @@
return nil;
}
+- (UIEdgeInsets)qt_safeAreaInsets
+{
+#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_NA, 110000, 110000, __WATCHOS_NA)
+ if (__builtin_available(iOS 11, tvOS 11, *))
+ return self.safeAreaInsets;
+#endif
+
+ // Fallback for iOS < 11
+ UIEdgeInsets safeAreaInsets = UIEdgeInsetsZero;
+ CGPoint topInset = [self convertPoint:CGPointMake(0, self.viewController.topLayoutGuide.length) fromView:nil];
+ CGPoint bottomInset = [self convertPoint:CGPointMake(0, self.viewController.bottomLayoutGuide.length) fromView:nil];
+ safeAreaInsets.top = topInset.y;
+ safeAreaInsets.bottom = bottomInset.y;
+ return safeAreaInsets;
+}
+
@end
#ifndef QT_NO_ACCESSIBILITY