summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios/qioseventdispatcher.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/ios/qioseventdispatcher.mm')
-rw-r--r--src/plugins/platforms/ios/qioseventdispatcher.mm622
1 files changed, 411 insertions, 211 deletions
diff --git a/src/plugins/platforms/ios/qioseventdispatcher.mm b/src/plugins/platforms/ios/qioseventdispatcher.mm
index e9bf039047..6148338000 100644
--- a/src/plugins/platforms/ios/qioseventdispatcher.mm
+++ b/src/plugins/platforms/ios/qioseventdispatcher.mm
@@ -40,283 +40,483 @@
****************************************************************************/
#include "qioseventdispatcher.h"
-#import "qiosapplicationdelegate.h"
-#include <qdebug.h>
-#include <qpa/qwindowsysteminterface.h>
-#include <QtCore/QThread>
+#include "qiosapplicationdelegate.h"
+#include "qiosglobal.h"
+
+#include <QtCore/qprocessordetection.h>
#include <QtCore/private/qcoreapplication_p.h>
-#include <UIKit/UIApplication.h>
+#include <QtCore/private/qthread_p.h>
+
+#import <Foundation/NSArray.h>
+#import <Foundation/NSString.h>
+#import <Foundation/NSProcessInfo.h>
+#import <Foundation/NSThread.h>
+#import <Foundation/NSNotification.h>
+
+#import <UIKit/UIApplication.h>
+
+#include <setjmp.h> // Here be dragons
+
+#include <sys/mman.h>
+
+#define qAlignDown(val, align) val & ~(align - 1)
+#define qAlignUp(val, align) qAlignDown(val + (align - 1), align)
+
+static const size_t kBytesPerKiloByte = 1024;
+static const long kPageSize = sysconf(_SC_PAGESIZE);
+
+/*
+ The following diagram shows the layout of the reserved
+ stack in relation to the regular stack, and the basic
+ flow of the initial startup sequence. Note how we end
+ up back in applicationDidLaunch after the user's main
+ recurses into qApp-exec(), which allows us to return
+ from applicationDidLaunch and spin the run-loop at the
+ same level (UIApplicationMain) as iOS nativly does.
+
+ +-----------------------------+
+ | qtmn() |
+ | +--------------------+ <-- base
+ | +----> main() | |
+ | | +--------------------+ |
+ | | | ... | |
+ | | +--------------------+ |
+ | | | qApp->exec() | |
+ | | +--------------------+ |
+ | | | processEvents() | |
+ | | | | |
+ | | +--+ longjmp(a) | |
+ | | | | | |
+ | | | +--------------------+ |
+ | | | | | |
+ | | | | | |
+ | | | | unused | |
+ | | | | | |
+ | | | | | |
+ | | | +--------------------+ <-- limit
+ | | | | memory guard | |
+ | | | +--------------------+ <-- reservedStack
+ +-|-|-------------------------+
+ | | | UIApplicationMain() |
+ +-|-|-------------------------+
+ | | | applicationDidLaunch() |
+ | | | |
+ | | +--> setjmp(a) |
+ | +----+ trampoline() |
+ | |
+ +-----------------------------+
+
+ Note: the diagram does not reflect alignment issues.
+*/
+
+namespace
+{
+ struct Stack
+ {
+ uintptr_t base;
+ uintptr_t limit;
-QT_BEGIN_NAMESPACE
-QT_USE_NAMESPACE
+ static size_t computeSize(size_t requestedSize)
+ {
+ if (!requestedSize)
+ return 0;
-static Boolean runLoopSourceEqualCallback(const void *info1, const void *info2)
-{
- return info1 == info2;
-}
+ // The stack size must be a multiple of 4 KB
+ size_t stackSize = qAlignUp(requestedSize, 4 * kBytesPerKiloByte);
-void QIOSEventDispatcher::postedEventsRunLoopCallback(void *info)
-{
- QIOSEventDispatcher *self = static_cast<QIOSEventDispatcher *>(info);
- self->processPostedEvents();
-}
+ // Be at least 16 KB
+ stackSize = qMax(16 * kBytesPerKiloByte, stackSize);
-void QIOSEventDispatcher::nonBlockingTimerRunLoopCallback(CFRunLoopTimerRef, void *info)
-{
- // The (one and only) CFRunLoopTimer has fired, which means that at least
- // one QTimer should now fire as well. Note that CFRunLoopTimer's callback will
- // never recurse. So if the app starts a new QEventLoop within this callback, other
- // timers will stop working. The work-around is to forward the callback to a
- // dedicated CFRunLoopSource that can recurse:
- QIOSEventDispatcher *self = static_cast<QIOSEventDispatcher *>(info);
- CFRunLoopSourceSignal(self->m_blockingTimerRunLoopSource);
-}
+ // Have enough extra space for our (aligned) memory guard
+ stackSize += (2 * kPageSize);
-void QIOSEventDispatcher::blockingTimerRunLoopCallback(void *info)
-{
- // TODO:
- // We also need to block this new timer source
- // along with the posted event source when calling processEvents()
- // "manually" to prevent livelock deep in CFRunLoop.
-
- QIOSEventDispatcher *self = static_cast<QIOSEventDispatcher *>(info);
- self->m_timerInfoList.activateTimers();
- self->maybeStartCFRunLoopTimer();
-}
+ // But not exceed the 1MB maximum (adjusted to account for current stack usage)
+ stackSize = qMin(stackSize, ((1024 - 64) * kBytesPerKiloByte));
-void QIOSEventDispatcher::maybeStartCFRunLoopTimer()
-{
- // Find out when the next registered timer should fire, and schedule
- // runLoopTimer accordingly. If the runLoopTimer does not yet exist, and
- // at least one timer is registered, start by creating the timer:
- if (m_timerInfoList.isEmpty()) {
- Q_ASSERT(m_runLoopTimerRef == 0);
- return;
- }
+ // Which we verify, just in case
+ struct rlimit stackLimit = {0, 0};
+ if (getrlimit(RLIMIT_STACK, &stackLimit) == 0 && stackSize > stackLimit.rlim_cur)
+ qFatal("Unexpectedly exceeded stack limit");
- CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
- CFTimeInterval interval;
-
- if (m_runLoopTimerRef == 0) {
- // start the CFRunLoopTimer
- CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.);
-
- // calculate when the next timer should fire:
- struct timespec tv;
- if (m_timerInfoList.timerWait(tv)) {
- interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001);
- } else {
- // this shouldn't really happen, but in case it does, set the timer
- // to fire a some point in the distant future:
- interval = oneyear;
+ return stackSize;
}
- ttf += interval;
- CFRunLoopTimerContext info = { 0, this, 0, 0, 0 };
- // create the timer with a large interval, as recommended by the CFRunLoopTimerSetNextFireDate()
- // documentation, since we will adjust the timer's time-to-fire as needed to keep Qt timers working
- m_runLoopTimerRef = CFRunLoopTimerCreate(0, ttf, oneyear, 0, 0, QIOSEventDispatcher::nonBlockingTimerRunLoopCallback, &info);
- Q_ASSERT(m_runLoopTimerRef != 0);
-
- CFRunLoopAddTimer(CFRunLoopGetMain(), m_runLoopTimerRef, kCFRunLoopCommonModes);
- } else {
- struct timespec tv;
- // Calculate when the next timer should fire:
- if (m_timerInfoList.timerWait(tv)) {
- interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001);
- } else {
- // no timers can fire, but we cannot stop the CFRunLoopTimer, set the timer to fire at some
- // point in the distant future (the timer interval is one year)
- interval = CFRunLoopTimerGetInterval(m_runLoopTimerRef);
+ void adopt(void* memory, size_t size)
+ {
+ uintptr_t memoryStart = uintptr_t(memory);
+
+ // Add memory guard at the end of the reserved stack, so that any stack
+ // overflow during the user's main will trigger an exception at that point,
+ // and not when we return and find that the current stack has been smashed.
+ uintptr_t memoryGuardStart = qAlignUp(memoryStart, kPageSize);
+ if (mprotect((void*)memoryGuardStart, kPageSize, PROT_NONE))
+ qWarning() << "Failed to add memory guard:" << strerror(errno);
+
+ // We don't consider the memory guard part of the usable stack space
+ limit = memoryGuardStart + kPageSize;
+
+ // The stack grows downwards in memory, so the stack base is actually
+ // at the end of the reserved stack space. And, as the memory guard
+ // was page aligned, we need to align down the base as well, to
+ // keep the requirement that the stack size is a multiple of 4K.
+ base = qAlignDown(memoryStart + size, kPageSize);
}
- ttf += interval;
- CFRunLoopTimerSetNextFireDate(m_runLoopTimerRef, ttf);
- }
-}
+ bool isValid()
+ {
+ return base && limit;
+ }
-void QIOSEventDispatcher::maybeStopCFRunLoopTimer()
-{
- if (m_runLoopTimerRef == 0)
- return;
+ size_t size()
+ {
+ return base - limit;
+ }
- CFRunLoopTimerInvalidate(m_runLoopTimerRef);
- CFRelease(m_runLoopTimerRef);
- m_runLoopTimerRef = 0;
-}
+ static const int kScribblePattern;
-void QIOSEventDispatcher::processPostedEvents()
-{
- QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::AllEvents);
+ void scribble()
+ {
+ memset_pattern4((void*)limit, &kScribblePattern, size());
+ }
+
+ void printUsage()
+ {
+ uintptr_t highWaterMark = limit;
+ for (; highWaterMark < base; highWaterMark += 4) {
+ if (memcmp((void*)highWaterMark, &kScribblePattern, 4))
+ break;
+ }
+
+ qDebug("main() used roughly %lu bytes of stack space", (base - highWaterMark));
+ }
+ };
+
+ const int Stack::kScribblePattern = 0xfafafafa;
+
+ Stack userMainStack;
+
+ jmp_buf processEventEnterJumpPoint;
+ jmp_buf processEventExitJumpPoint;
+
+ bool applicationAboutToTerminate = false;
+ jmp_buf applicationWillTerminateJumpPoint;
+
+ bool debugStackUsage = false;
}
-QIOSEventDispatcher::QIOSEventDispatcher(QObject *parent)
- : QAbstractEventDispatcher(parent)
- , m_interrupted(false)
- , m_runLoopTimerRef(0)
+static int infoPlistValue(NSString* key, int defaultValue)
{
- m_cfSocketNotifier.setHostEventDispatcher(this);
-
- CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
- CFRunLoopSourceContext context;
- bzero(&context, sizeof(CFRunLoopSourceContext));
- context.equal = runLoopSourceEqualCallback;
- context.info = this;
-
- // source used to handle timers:
- context.perform = QIOSEventDispatcher::blockingTimerRunLoopCallback;
- m_blockingTimerRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
- Q_ASSERT(m_blockingTimerRunLoopSource);
- CFRunLoopAddSource(mainRunLoop, m_blockingTimerRunLoopSource, kCFRunLoopCommonModes);
-
- // source used to handle posted events:
- context.perform = QIOSEventDispatcher::postedEventsRunLoopCallback;
- m_postedEventsRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
- Q_ASSERT(m_postedEventsRunLoopSource);
- CFRunLoopAddSource(mainRunLoop, m_postedEventsRunLoopSource, kCFRunLoopCommonModes);
+ static NSBundle *bundle = [NSBundle mainBundle];
+ NSNumber* value = [bundle objectForInfoDictionaryKey:key];
+ return value ? [value intValue] : defaultValue;
}
-QIOSEventDispatcher::~QIOSEventDispatcher()
+extern "C" int qtmn(int argc, char *argv[])
{
- CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
- CFRunLoopRemoveSource(mainRunLoop, m_postedEventsRunLoopSource, kCFRunLoopCommonModes);
- CFRelease(m_postedEventsRunLoopSource);
+ @autoreleasepool {
+ size_t defaultStackSize = 512 * kBytesPerKiloByte; // Same as secondary threads
- qDeleteAll(m_timerInfoList);
- maybeStopCFRunLoopTimer();
- CFRunLoopRemoveSource(CFRunLoopGetMain(), m_blockingTimerRunLoopSource, kCFRunLoopCommonModes);
- CFRelease(m_blockingTimerRunLoopSource);
+ uint requestedStackSize = qMax(0, infoPlistValue(@"QtRunLoopIntegrationStackSize", defaultStackSize));
- m_cfSocketNotifier.removeSocketNotifiers();
-}
+ if (infoPlistValue(@"QtRunLoopIntegrationDisableSeparateStack", false))
+ requestedStackSize = 0;
-bool QIOSEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
-{
- m_interrupted = false;
- bool eventsProcessed = false;
-
- bool excludeUserEvents = flags & QEventLoop::ExcludeUserInputEvents;
- bool execFlagSet = (flags & QEventLoop::DialogExec) || (flags & QEventLoop::EventLoopExec);
- bool useExecMode = execFlagSet && !excludeUserEvents;
-
- if (useExecMode) {
- NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
- while ([runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]] && !m_interrupted);
- eventsProcessed = true;
- } else {
- if (!(flags & QEventLoop::WaitForMoreEvents))
- wakeUp();
- eventsProcessed = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
+ char reservedStack[Stack::computeSize(requestedStackSize)];
+
+ if (sizeof(reservedStack) > 0) {
+ userMainStack.adopt(reservedStack, sizeof(reservedStack));
+
+ if (infoPlistValue(@"QtRunLoopIntegrationDebugStackUsage", false)) {
+ debugStackUsage = true;
+ userMainStack.scribble();
+ qDebug("Effective stack size is %lu bytes", userMainStack.size());
+ }
+ }
+
+ qEventDispatcherDebug() << "Running UIApplicationMain"; qIndent();
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([QIOSApplicationDelegate class]));
}
- return eventsProcessed;
}
-bool QIOSEventDispatcher::hasPendingEvents()
+enum SetJumpResult
{
- qDebug() << __FUNCTION__ << "not implemented";
- return false;
+ kJumpPointSetSuccessfully = 0,
+ kJumpedFromEventDispatcherProcessEvents,
+ kJumpedFromQApplicationExecInterrupt,
+ kJumpedFromUserMainTrampoline,
+};
+
+extern "C" int main(int argc, char *argv[]);
+
+static void __attribute__((noinline, noreturn)) user_main_trampoline()
+{
+ NSArray *arguments = [[NSProcessInfo processInfo] arguments];
+ int argc = arguments.count;
+ char **argv = new char*[argc];
+ for (int i = 0; i < argc; ++i) {
+ NSString *arg = [arguments objectAtIndex:i];
+ argv[i] = reinterpret_cast<char *>(malloc([arg lengthOfBytesUsingEncoding:[NSString defaultCStringEncoding]]));
+ strcpy(argv[i], [arg cStringUsingEncoding:[NSString defaultCStringEncoding]]);
+ }
+
+ int exitCode = main(argc, argv);
+ delete[] argv;
+
+ qEventDispatcherDebug() << "Returned from main with exit code " << exitCode;
+
+ if (Q_UNLIKELY(debugStackUsage))
+ userMainStack.printUsage();
+
+ if (applicationAboutToTerminate)
+ longjmp(applicationWillTerminateJumpPoint, kJumpedFromUserMainTrampoline);
+
+ // We end up here if the user's main() never calls QApplication::exec(),
+ // or if we return from exec() after quitting the application. If so we
+ // follow the expected behavior from the point of the user's main(), which
+ // is to exit with the given exit code.
+ exit(exitCode);
}
-void QIOSEventDispatcher::registerSocketNotifier(QSocketNotifier *notifier)
+// If we don't have a stack set up, we're not running inside
+// iOS' native/root level run-loop in UIApplicationMain.
+static bool rootLevelRunLoopIntegration()
{
- m_cfSocketNotifier.registerSocketNotifier(notifier);
+ return userMainStack.isValid();
}
-void QIOSEventDispatcher::unregisterSocketNotifier(QSocketNotifier *notifier)
+@interface QIOSApplicationStateTracker : NSObject
+@end
+
+@implementation QIOSApplicationStateTracker
+
++ (void) load
{
- m_cfSocketNotifier.unregisterSocketNotifier(notifier);
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(applicationDidFinishLaunching)
+ name:UIApplicationDidFinishLaunchingNotification
+ object:nil];
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(applicationWillTerminate)
+ name:UIApplicationWillTerminateNotification
+ object:nil];
}
-void QIOSEventDispatcher::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *obj)
+#if defined(Q_PROCESSOR_X86)
+# define SET_STACK_POINTER "mov %0, %%esp"
+# define FUNCTION_CALL_ALIGNMENT 16
+#elif defined(Q_PROCESSOR_ARM)
+# define SET_STACK_POINTER "mov sp, %0"
+# define FUNCTION_CALL_ALIGNMENT 4
+#else
+# error "Unknown processor family"
+#endif
+
++ (void) applicationDidFinishLaunching
{
-#ifndef QT_NO_DEBUG
- if (timerId < 1 || interval < 0 || !obj) {
- qWarning("QIOSEventDispatcher::registerTimer: invalid arguments");
+ if (!isQtApplication())
return;
- } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
- qWarning("QIOSEventDispatcher: timers cannot be started from another thread");
+
+ if (!rootLevelRunLoopIntegration()) {
+ // We schedule the main-redirection for the next run-loop pass, so that we
+ // can return from this function and let UIApplicationMain finish its job.
+ // This results in running Qt's application eventloop as a nested runloop.
+ qEventDispatcherDebug() << "Scheduling main() on next run-loop pass";
+ CFRunLoopTimerRef userMainTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault,
+ CFAbsoluteTimeGetCurrent(), 0, 0, 0, ^(CFRunLoopTimerRef) { user_main_trampoline(); });
+ CFRunLoopAddTimer(CFRunLoopGetMain(), userMainTimer, kCFRunLoopCommonModes);
+ CFRelease(userMainTimer);
return;
}
-#endif
- m_timerInfoList.registerTimer(timerId, interval, timerType, obj);
- maybeStartCFRunLoopTimer();
+ switch (setjmp(processEventEnterJumpPoint)) {
+ case kJumpPointSetSuccessfully:
+ qEventDispatcherDebug() << "Running main() on separate stack"; qIndent();
+
+ // Redirect the stack pointer to the start of the reserved stack. This ensures
+ // that when we longjmp out of the event dispatcher and continue execution, the
+ // 'Qt main' call-stack will not be smashed, as it lives in a part of the stack
+ // that was allocated back in main().
+ __asm__ __volatile__(
+ SET_STACK_POINTER
+ : /* no outputs */
+ : "r" (qAlignDown(userMainStack.base, FUNCTION_CALL_ALIGNMENT))
+ );
+
+ user_main_trampoline();
+
+ Q_UNREACHABLE();
+ break;
+ case kJumpedFromEventDispatcherProcessEvents:
+ // We've returned from the longjmp in the event dispatcher,
+ // and the stack has been restored to its old self.
+ qUnIndent(); qEventDispatcherDebug() << "Returned from processEvents";
+
+ if (Q_UNLIKELY(debugStackUsage))
+ userMainStack.printUsage();
+
+ break;
+ default:
+ qFatal("Unexpected jump result in event loop integration");
+ }
}
-bool QIOSEventDispatcher::unregisterTimer(int timerId)
++ (void) applicationWillTerminate
{
-#ifndef QT_NO_DEBUG
- if (timerId < 1) {
- qWarning("QIOSEventDispatcher::unregisterTimer: invalid argument");
- return false;
- } else if (thread() != QThread::currentThread()) {
- qWarning("QObject::killTimer: timers cannot be stopped from another thread");
- return false;
- }
-#endif
+ if (!isQtApplication())
+ return;
- bool returnValue = m_timerInfoList.unregisterTimer(timerId);
- m_timerInfoList.isEmpty() ? maybeStopCFRunLoopTimer() : maybeStartCFRunLoopTimer();
- return returnValue;
-}
+ if (!rootLevelRunLoopIntegration())
+ return;
-bool QIOSEventDispatcher::unregisterTimers(QObject *object)
-{
-#ifndef QT_NO_DEBUG
- if (!object) {
- qWarning("QIOSEventDispatcher::unregisterTimers: invalid argument");
- return false;
- } else if (object->thread() != thread() || thread() != QThread::currentThread()) {
- qWarning("QObject::killTimers: timers cannot be stopped from another thread");
- return false;
+ // Normally iOS just sends SIGKILL to quit applications, in which case there's
+ // no chance for us to clean up anything, but in some rare cases iOS will tell
+ // us that the application is about to be terminated.
+
+ // We try to play nice with Qt by ending the main event loop, which will result
+ // in QCoreApplication::aboutToQuit() being emitted, and main() returning to the
+ // trampoline. The trampoline then redirects us back here, so that we can return
+ // to UIApplicationMain instead of calling exit().
+
+ applicationAboutToTerminate = true;
+ switch (setjmp(applicationWillTerminateJumpPoint)) {
+ case kJumpPointSetSuccessfully:
+ qEventDispatcherDebug() << "Exiting qApp with SIGTERM exit code"; qIndent();
+ // We treat applicationWillTerminate as SIGTERM, even if it can't be ignored
+ qApp->exit(128 + SIGTERM);
+
+ // The runloop will not exit when the application is about to terminate,
+ // so we'll never see the exit activity and have a chance to return from
+ // QApplication::exec(). We initiate the return manually as a workaround.
+ qEventDispatcherDebug() << "Manually triggering return from QApp exec";
+ static_cast<QIOSEventDispatcher *>(qApp->eventDispatcher())->interruptQApplicationExec();
+ break;
+ case kJumpedFromUserMainTrampoline:
+ // The user's main has returned, so we're ready to let iOS terminate the application
+ qUnIndent(); qEventDispatcherDebug() << "kJumpedFromUserMainTrampoline, allowing iOS to terminate";
+ break;
+ default:
+ qFatal("Unexpected jump result in event loop integration");
}
-#endif
+}
+
+@end
- bool returnValue = m_timerInfoList.unregisterTimers(object);
- m_timerInfoList.isEmpty() ? maybeStopCFRunLoopTimer() : maybeStartCFRunLoopTimer();
- return returnValue;
+QT_BEGIN_NAMESPACE
+QT_USE_NAMESPACE
+
+QIOSEventDispatcher::QIOSEventDispatcher(QObject *parent)
+ : QEventDispatcherCoreFoundation(parent)
+ , m_processEventCallsAfterAppExec(0)
+ , m_runLoopExitObserver(this, &QIOSEventDispatcher::handleRunLoopExit, kCFRunLoopExit)
+{
}
-QList<QAbstractEventDispatcher::TimerInfo> QIOSEventDispatcher::registeredTimers(QObject *object) const
+bool __attribute__((returns_twice)) QIOSEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
{
-#ifndef QT_NO_DEBUG
- if (!object) {
- qWarning("QIOSEventDispatcher:registeredTimers: invalid argument");
- return QList<TimerInfo>();
+ if (!rootLevelRunLoopIntegration())
+ return QEventDispatcherCoreFoundation::processEvents(flags);
+
+ QCoreApplicationPrivate *qApplication = static_cast<QCoreApplicationPrivate *>(QObjectPrivate::get(qApp));
+ if (!m_processEventCallsAfterAppExec && qApplication->in_exec) {
+ Q_ASSERT(flags & QEventLoop::EventLoopExec);
+
+ // We know that app->in_exec is set just before executing the main event loop,
+ // so the first processEvents call after that will be the main event loop.
+ ++m_processEventCallsAfterAppExec;
+
+ // We set a new jump point here that we can return to when the Qt application
+ // is asked to exit, so that we can return from QCoreApplication::exec().
+ switch (setjmp(processEventExitJumpPoint)) {
+ case kJumpPointSetSuccessfully:
+ qEventDispatcherDebug() << "QApplication exec detected, jumping back to native runloop";
+ longjmp(processEventEnterJumpPoint, kJumpedFromEventDispatcherProcessEvents);
+ break;
+ case kJumpedFromQApplicationExecInterrupt:
+ // QCoreApplication has quit (either by the hand of the user, or the iOS termination
+ // signal), and we jumped back though processEventExitJumpPoint. We return from processEvents,
+ // which will emit aboutToQuit and then return to the user's main, which can do
+ // whatever it wants, including calling exec() on the application again.
+ qEventDispatcherDebug() << "kJumpedFromQApplicationExecInterrupt, returning with eventsProcessed = true";
+ return true;
+ default:
+ qFatal("Unexpected jump result in event loop integration");
+ }
+
+ Q_UNREACHABLE();
}
-#endif
- return m_timerInfoList.registeredTimers(object);
+ if (m_processEventCallsAfterAppExec)
+ ++m_processEventCallsAfterAppExec;
+
+ bool processedEvents = QEventDispatcherCoreFoundation::processEvents(flags);
+
+ if (m_processEventCallsAfterAppExec)
+ --m_processEventCallsAfterAppExec;
+
+ // If we're running with nested event loops and the application is quit,
+ // then the forwarded interrupt call will happen while our processEvent
+ // counter is still 2, and we won't detect that we're about to fall down
+ // to the root iOS run-loop. We do an extra check here to catch that case.
+ checkIfApplicationShouldQuit();
+
+ return processedEvents;
}
-int QIOSEventDispatcher::remainingTime(int timerId)
+void QIOSEventDispatcher::interrupt()
{
-#ifndef QT_NO_DEBUG
- if (timerId < 1) {
- qWarning("QIOSEventDispatcher::remainingTime: invalid argument");
- return -1;
- }
-#endif
+ QEventDispatcherCoreFoundation::interrupt();
- return m_timerInfoList.timerRemainingTime(timerId);
+ if (!rootLevelRunLoopIntegration())
+ return;
+
+ // If an interrupt happens as part of a non-nested event loop, that is,
+ // by processing an event or timer in the root iOS run-loop, we'll be
+ // able to detect it here.
+ checkIfApplicationShouldQuit();
}
-void QIOSEventDispatcher::wakeUp()
+void QIOSEventDispatcher::checkIfApplicationShouldQuit()
{
- CFRunLoopSourceSignal(m_postedEventsRunLoopSource);
- CFRunLoopWakeUp(CFRunLoopGetMain());
+ if (QThreadData::current()->quitNow && m_processEventCallsAfterAppExec == 1) {
+ qEventDispatcherDebug() << "Hit root runloop level, watching for runloop exit";
+ m_runLoopExitObserver.addToMode(kCFRunLoopCommonModes);
+ }
}
-void QIOSEventDispatcher::interrupt()
+void QIOSEventDispatcher::handleRunLoopExit(CFRunLoopActivity activity)
{
- wakeUp();
- m_interrupted = true;
+ Q_ASSERT(activity == kCFRunLoopExit);
+
+ m_runLoopExitObserver.removeFromMode(kCFRunLoopCommonModes);
+
+ interruptQApplicationExec();
}
-void QIOSEventDispatcher::flush()
+void QIOSEventDispatcher::interruptQApplicationExec()
{
- // X11 only.
+ Q_ASSERT(QThreadData::current()->quitNow);
+ Q_ASSERT(m_processEventCallsAfterAppExec == 1);
+
+ --m_processEventCallsAfterAppExec;
+
+ // We re-set applicationProcessEventsReturnPoint here so that future
+ // calls to QApplication::exec() will end up back here after entering
+ // processEvents, instead of back in didFinishLaunchingWithOptions.
+ switch (setjmp(processEventEnterJumpPoint)) {
+ case kJumpPointSetSuccessfully:
+ qEventDispatcherDebug() << "Jumping back to processEvents";
+ longjmp(processEventExitJumpPoint, kJumpedFromQApplicationExecInterrupt);
+ break;
+ case kJumpedFromEventDispatcherProcessEvents:
+ // QCoreApplication was re-executed
+ qEventDispatcherDebug() << "kJumpedFromEventDispatcherProcessEvents";
+ break;
+ default:
+ qFatal("Unexpected jump result in event loop integration");
+ }
}
QT_END_NAMESPACE
-