summaryrefslogtreecommitdiffstats
path: root/src/platformsupport/eventdispatchers
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@digia.com>2013-08-15 12:53:16 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-12 12:32:14 +0200
commit1ec37184329afa1c76141a3663f6adb5536fd051 (patch)
treeaf566ead6ae12d0f7e8a8205e219a12fb022d796 /src/platformsupport/eventdispatchers
parentfc538bce4a14a184f625208d8c06d1a874d8e0a2 (diff)
iOS: Rewrite CoreFoundation event-dispatcher
Instead of having separate code-paths for QEventLoop::EventLoopExec and the non-blocking processEvent() we now have a single Q_FOREVER loop where the logic can be shared. We make multiple loop-passes, each time calling CFRunLoopRunInMode with potentially different arguments, depending on the result of the previous run. For the EventLoopExec case we'll continue making loop-passes until the event-loop has been interrupted. For the non-EventLoopExec case, we respect interruption, but will continue making loop-passes only until we've processed all events in the queue, optionally waiting for the initial event if WaitForMoreEvents is set. Limitations in the CoreFoundation APIs unfortunately force us to keep some state on whether or not we've processed events and timers for a given processEvents() pass (with corresponding deferred scheduling of timers and event source signaling). The way we handle timers has also been rewritten to no longer defer the timer activation to a special timer source. The constraint of CoreFoundation timers is that they can not recurse (re-fire) in a callback, but that only applies per timer, so using multiple CF timers allows us to recurse. We still only use a single CF timer for all the Qt timers though, and only spawn a new CF timer if the user calls processEvents() inside a timer callback. This commit removes the logic related to dealing with UITrackingMode, as that logic was slighly problematic, but the feature will be added back in a follow-up commit, in line with the new approach. The result of this commit is that we're passing all event-loop, event-dispatcher, and timer auto-tests, both for the QtCore dispatcher and the GUI event-dispatcher. Change-Id: I3c56fbc7857a25110064681257abb47075b5bd2d Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@digia.com>
Diffstat (limited to 'src/platformsupport/eventdispatchers')
-rw-r--r--src/platformsupport/eventdispatchers/qeventdispatcher_cf.mm591
-rw-r--r--src/platformsupport/eventdispatchers/qeventdispatcher_cf_p.h51
2 files changed, 437 insertions, 205 deletions
diff --git a/src/platformsupport/eventdispatchers/qeventdispatcher_cf.mm b/src/platformsupport/eventdispatchers/qeventdispatcher_cf.mm
index 78f97d5556..c0080a0daa 100644
--- a/src/platformsupport/eventdispatchers/qeventdispatcher_cf.mm
+++ b/src/platformsupport/eventdispatchers/qeventdispatcher_cf.mm
@@ -40,10 +40,16 @@
****************************************************************************/
#include "qeventdispatcher_cf_p.h"
-#include <qdebug.h>
-#include <qpa/qwindowsysteminterface.h>
-#include <QtCore/QThread>
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qthread.h>
#include <QtCore/private/qcoreapplication_p.h>
+#include <QtCore/private/qcore_unix_p.h>
+#include <QtCore/private/qcore_mac_p.h>
+#include <QtCore/private/qthread_p.h>
+
+#include <qpa/qwindowsysteminterface.h>
#include <limits>
@@ -52,201 +58,382 @@
QT_BEGIN_NAMESPACE
QT_USE_NAMESPACE
+class RunLoopDebugger : public QObject
+{
+ Q_OBJECT
+
+ Q_ENUMS(Activity)
+ Q_ENUMS(Result)
+
+public:
+
+ #define Q_MIRROR_ENUM(name) name = name
+
+ enum Activity {
+ Q_MIRROR_ENUM(kCFRunLoopEntry),
+ Q_MIRROR_ENUM(kCFRunLoopBeforeTimers),
+ Q_MIRROR_ENUM(kCFRunLoopBeforeSources),
+ Q_MIRROR_ENUM(kCFRunLoopBeforeWaiting),
+ Q_MIRROR_ENUM(kCFRunLoopAfterWaiting),
+ Q_MIRROR_ENUM(kCFRunLoopExit)
+ };
+
+ enum Result {
+ Q_MIRROR_ENUM(kCFRunLoopRunFinished),
+ Q_MIRROR_ENUM(kCFRunLoopRunStopped),
+ Q_MIRROR_ENUM(kCFRunLoopRunTimedOut),
+ Q_MIRROR_ENUM(kCFRunLoopRunHandledSource)
+ };
+};
+
+#define Q_ENUM_PRINTER(enumName) \
+ static const char* qPrintable##enumName(int value) \
+ { \
+ return RunLoopDebugger::staticMetaObject.enumerator(RunLoopDebugger::staticMetaObject.indexOfEnumerator(#enumName)).valueToKey(value); \
+ }
+
+Q_ENUM_PRINTER(Activity);
+Q_ENUM_PRINTER(Result);
+
+QDebug operator<<(QDebug s, timespec tv)
+{
+ s << tv.tv_sec << "." << qSetFieldWidth(9) << qSetPadChar(QChar(48)) << tv.tv_nsec << reset;
+ return s;
+}
+
+#if DEBUG_EVENT_DISPATCHER
+uint g_eventDispatcherIndentationLevel = 0;
+#endif
+
static const CFTimeInterval kCFTimeIntervalMinimum = 0;
static const CFTimeInterval kCFTimeIntervalDistantFuture = std::numeric_limits<CFTimeInterval>::max();
-void QEventDispatcherCoreFoundation::nonBlockingTimerRunLoopCallback(CFRunLoopTimerRef, void *info)
+#pragma mark - Class definition
+
+QEventDispatcherCoreFoundation::QEventDispatcherCoreFoundation(QObject *parent)
+ : QAbstractEventDispatcher(parent)
+ , m_postedEventsRunLoopSource(this, &QEventDispatcherCoreFoundation::processPostedEvents)
+ , m_runLoopActivityObserver(this, &QEventDispatcherCoreFoundation::handleRunLoopActivity,
+#if DEBUG_EVENT_DISPATCHER
+ kCFRunLoopAllActivities
+#else
+ kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting
+#endif
+ )
+ , m_runLoopTimer(0)
+ , m_blockedRunLoopTimer(0)
+ , m_overdueTimerScheduled(false)
+ , m_processEvents(QEventLoop::EventLoopExec)
{
- // 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:
- QEventDispatcherCoreFoundation *self = static_cast<QEventDispatcherCoreFoundation *>(info);
- self->m_blockingTimerRunLoopSource.signal();
- // FIXME: And not wake up main run loop?
+ m_cfSocketNotifier.setHostEventDispatcher(this);
+
+ m_postedEventsRunLoopSource.addToMode(kCFRunLoopCommonModes);
+ m_runLoopActivityObserver.addToMode(kCFRunLoopCommonModes);
}
-void QEventDispatcherCoreFoundation::maybeStartCFRunLoopTimer()
+QEventDispatcherCoreFoundation::~QEventDispatcherCoreFoundation()
{
- // 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;
+ invalidateTimer();
+ qDeleteAll(m_timerInfoList);
+
+ m_cfSocketNotifier.removeSocketNotifiers();
+}
+
+/*!
+ Processes all pending events that match \a flags until there are no
+ more events to process. Returns true if pending events were handled;
+ otherwise returns false.
+
+ Note:
+
+ - All events are considered equal. This function should process
+ both system/native events (that we may or may not care about),
+ as well as Qt-events (posted events and timers).
+
+ - The function should not return until all queued/available events
+ have been processed. If the WaitForMoreEvents is set, the
+ function should wait only if there were no events ready,
+ and _then_ process all newly queued/available events.
+
+ These notes apply to other function in this class as well, such as
+ hasPendingEvents().
+*/
+bool QEventDispatcherCoreFoundation::processEvents(QEventLoop::ProcessEventsFlags flags)
+{
+ bool eventsProcessed = false;
+
+ if (flags & (QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers))
+ qWarning() << "processEvents() flags" << flags << "not supported on iOS";
+
+ qEventDispatcherDebug() << "Entering with " << flags; qIndent();
+
+ if (m_blockedRunLoopTimer) {
+ Q_ASSERT(m_blockedRunLoopTimer == m_runLoopTimer);
+
+ qEventDispatcherDebug() << "Recursing from blocked timer " << m_blockedRunLoopTimer;
+ m_runLoopTimer = 0; // Unset current timer to force creation of new timer
+ updateTimers();
}
- CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
- CFTimeInterval interval;
+ if (m_processEvents.deferredWakeUp) {
+ // We may be processing events recursivly as a result of processing a posted event,
+ // in which case we need to signal the run-loop source so that this iteration of
+ // processEvents will take care of the newly posted events.
+ m_postedEventsRunLoopSource.signal();
+ m_processEvents.deferredWakeUp = false;
- if (m_runLoopTimerRef == 0) {
- // start the CFRunLoopTimer
+ qEventDispatcherDebug() << "Processed deferred wake-up";
+ }
- // 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 = kCFTimeIntervalDistantFuture;
- }
+ // The documentation states that this signal is emitted after the event
+ // loop returns from a function that could block, which is not the case
+ // here, but all the other event dispatchers emit awake at the start of
+ // processEvents, and the QEventLoop auto-test has an explicit check for
+ // this behavior, so we assume it's for a good reason and do it as well.
+ emit awake();
- 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, kCFTimeIntervalDistantFuture, 0, 0, QEventDispatcherCoreFoundation::nonBlockingTimerRunLoopCallback, &info);
- Q_ASSERT(m_runLoopTimerRef != 0);
+ ProcessEventsState previousState = m_processEvents;
+ m_processEvents = ProcessEventsState(flags);
- CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
- CFRunLoopAddTimer(mainRunLoop, m_runLoopTimerRef, kCFRunLoopCommonModes);
- CFRunLoopAddTimer(mainRunLoop, m_runLoopTimerRef, (CFStringRef) UITrackingRunLoopMode);
- } 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);
+ CFStringRef mode = kCFRunLoopDefaultMode;
+
+ bool returnAfterSingleSourceHandled = !(m_processEvents.flags & QEventLoop::EventLoopExec);
+
+ Q_FOREVER {
+ CFTimeInterval duration = (m_processEvents.flags & QEventLoop::WaitForMoreEvents) ?
+ kCFTimeIntervalDistantFuture : kCFTimeIntervalMinimum;
+
+ qEventDispatcherDebug() << "Calling CFRunLoopRunInMode = " << qPrintable(QCFString::toQString(mode))
+ << " for " << duration << " ms, processing single source = " << returnAfterSingleSourceHandled; qIndent();
+
+ SInt32 result = CFRunLoopRunInMode(mode, duration, returnAfterSingleSourceHandled);
+
+ qUnIndent(); qEventDispatcherDebug() << "result = " << qPrintableResult(result);
+
+ if (result == kCFRunLoopRunFinished) {
+ // This should only happen at application shutdown, as the main runloop
+ // will presumably always have sources registered.
+ break;
+ } else if (m_processEvents.wasInterrupted) {
+
+ if (m_processEvents.flags & QEventLoop::EventLoopExec) {
+ Q_ASSERT(result == kCFRunLoopRunStopped);
+
+ // The runloop was potentially stopped (interrupted) by us, as a response to
+ // a Qt event loop being asked to exit. We check that the topmost eventloop
+ // is still supposed to keep going and return if not. Note that the runloop
+ // might get stopped as a result of a non-top eventloop being asked to exit,
+ // in which case we continue running the top event loop until that is asked
+ // to exit, and then unwind back to the previous event loop which will break
+ // immediately, since it has already been exited.
+
+ QEventLoop *currentEventLoop = QThreadData::current()->eventLoops.top();
+ Q_ASSERT(currentEventLoop);
+
+ if (!currentEventLoop->isRunning()) {
+ qEventDispatcherDebug() << "Top level event loop was exited";
+ break;
+ } else {
+ qEventDispatcherDebug() << "Top level event loop still running, making another pass";
+ }
+ } else {
+ // We were called manually, through processEvents(), and should stop processing
+ // events, even if we didn't finish processing all the queued events.
+ qEventDispatcherDebug() << "Top level processEvents was interrupted";
+ break;
+ }
+ }
+
+ eventsProcessed |= (result == kCFRunLoopRunHandledSource
+ || m_processEvents.processedPostedEvents
+ || m_processEvents.processedTimers);
+
+ if (m_processEvents.flags & QEventLoop::EventLoopExec) {
+ // We were called from QEventLoop's exec(), which blocks until the event
+ // loop is asked to exit by calling processEvents repeatedly. Instead of
+ // re-entering this method again and again from QEventLoop, we can block
+ // here, one lever closer to CFRunLoopRunInMode, by running the native
+ // event loop again and again until we're interrupted by QEventLoop.
+ continue;
} 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);
+ // We were called 'manually', through processEvents()
+
+ if (result == kCFRunLoopRunHandledSource) {
+ // We processed one or more sources, but there might still be other
+ // sources that did not get a chance to process events, so we need
+ // to do another pass.
+
+ // But we should only wait for more events the first time
+ m_processEvents.flags &= ~QEventLoop::WaitForMoreEvents;
+ continue;
+
+ } else if (m_overdueTimerScheduled && !m_processEvents.processedTimers) {
+ // CFRunLoopRunInMode does not guarantee that a scheduled timer with a fire
+ // date in the past (overdue) will fire on the next run loop pass. The Qt
+ // APIs on the other hand document eg. zero-interval timers to always be
+ // handled after processing all available window-system events.
+ qEventDispatcherDebug() << "Manually processing timers due to overdue timer";
+ processTimers(0);
+ eventsProcessed = true;
+ }
}
- ttf += interval;
- CFRunLoopTimerSetNextFireDate(m_runLoopTimerRef, ttf);
+ break;
}
-}
-void QEventDispatcherCoreFoundation::maybeStopCFRunLoopTimer()
-{
- if (m_runLoopTimerRef == 0)
- return;
+ if (m_blockedRunLoopTimer) {
+ invalidateTimer();
+ m_runLoopTimer = m_blockedRunLoopTimer;
+ }
- CFRunLoopTimerInvalidate(m_runLoopTimerRef);
- CFRelease(m_runLoopTimerRef);
- m_runLoopTimerRef = 0;
-}
+ if (m_processEvents.deferredUpdateTimers)
+ updateTimers();
-QEventDispatcherCoreFoundation::QEventDispatcherCoreFoundation(QObject *parent)
- : QAbstractEventDispatcher(parent)
- , m_interrupted(false)
- , m_postedEventsRunLoopSource(this, &QEventDispatcherCoreFoundation::processPostedEvents)
- , m_blockingTimerRunLoopSource(this, &QEventDispatcherCoreFoundation::processTimers)
- , m_awakeAndBlockObserver(this, &QEventDispatcherCoreFoundation::handleRunLoopActivity,
- kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting)
- , m_runLoopTimerRef(0)
-{
- m_cfSocketNotifier.setHostEventDispatcher(this);
+ if (m_processEvents.deferredWakeUp) {
+ m_postedEventsRunLoopSource.signal();
+ qEventDispatcherDebug() << "Processed deferred wake-up";
+ }
- m_postedEventsRunLoopSource.addToMode(kCFRunLoopCommonModes);
+ bool wasInterrupted = m_processEvents.wasInterrupted;
- m_blockingTimerRunLoopSource.addToMode(kCFRunLoopCommonModes);
- m_blockingTimerRunLoopSource.addToMode(CFStringRef(UITrackingRunLoopMode));
+ // Restore state of previous processEvents() call
+ m_processEvents = previousState;
- m_awakeAndBlockObserver.addToMode(kCFRunLoopCommonModes);
-}
+ if (wasInterrupted) {
+ // The current processEvents run has been interrupted, but there may still be
+ // others below it (eg, in the case of nested event loops). We need to trigger
+ // another interrupt so that the parent processEvents call has a chance to check
+ // if it should continue.
+ qEventDispatcherDebug() << "Forwarding interrupt in case of nested processEvents";
+ interrupt();
+ }
-QEventDispatcherCoreFoundation::~QEventDispatcherCoreFoundation()
-{
- qDeleteAll(m_timerInfoList);
- maybeStopCFRunLoopTimer();
+ qEventDispatcherDebug() << "Returning with eventsProcessed = " << eventsProcessed; qUnIndent();
- m_cfSocketNotifier.removeSocketNotifiers();
+ return eventsProcessed;
}
void QEventDispatcherCoreFoundation::processPostedEvents()
{
- QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::AllEvents);
+ if (m_processEvents.processedPostedEvents && !(m_processEvents.flags & QEventLoop::EventLoopExec)) {
+ qEventDispatcherDebug() << "Already processed events this pass";
+ return;
+ }
+
+ m_processEvents.processedPostedEvents = true;
+
+ qEventDispatcherDebug() << "Sending window system events for " << m_processEvents.flags; qIndent();
+ QWindowSystemInterface::sendWindowSystemEvents(m_processEvents.flags);
+ qUnIndent();
}
-void QEventDispatcherCoreFoundation::processTimers()
+void QEventDispatcherCoreFoundation::processTimers(CFRunLoopTimerRef timer)
{
- // 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.
+ if (m_processEvents.processedTimers && !(m_processEvents.flags & QEventLoop::EventLoopExec)) {
+ qEventDispatcherDebug() << "Already processed timers this pass";
+ m_processEvents.deferredUpdateTimers = true;
+ return;
+ }
+
+ qEventDispatcherDebug() << "CFRunLoopTimer " << timer << " fired, activating Qt timers"; qIndent();
+ // Activating Qt timers might recurse into processEvents() if a timer-callback
+ // brings up a new event-loop or tries to processes events manually. Although
+ // a CFRunLoop can recurse inside its callbacks, a single CFRunLoopTimer can
+ // not. So, for each recursion into processEvents() from a timer-callback we
+ // need to set up a new timer-source. Instead of doing it preemtivly each
+ // time we activate Qt timers, we set a flag here, and let processEvents()
+ // decide whether or not it needs to bring up a new timer source.
+
+ // We may have multiple recused timers, so keep track of the previous blocked timer
+ CFRunLoopTimerRef previouslyBlockedRunLoopTimer = m_blockedRunLoopTimer;
+
+ m_blockedRunLoopTimer = timer;
m_timerInfoList.activateTimers();
- maybeStartCFRunLoopTimer();
+ m_blockedRunLoopTimer = previouslyBlockedRunLoopTimer;
+ m_processEvents.processedTimers = true;
+
+ qUnIndent();
+
+ // Now that the timer source is unblocked we may need to schedule it again
+ updateTimers();
}
void QEventDispatcherCoreFoundation::handleRunLoopActivity(CFRunLoopActivity activity)
{
+ qEventDispatcherDebug() << qPrintableActivity(activity);
+
switch (activity) {
case kCFRunLoopBeforeWaiting:
+ if (m_processEvents.processedTimers
+ && !(m_processEvents.flags & QEventLoop::EventLoopExec)
+ && m_processEvents.flags & QEventLoop::WaitForMoreEvents) {
+ // CoreFoundation does not treat a timer as a reason to exit CFRunLoopRunInMode
+ // when asked to only process a single source, so we risk waiting a long time for
+ // a 'proper' source to fire (typically a system source that we don't control).
+ // To fix this we do an explicit interrupt after processing our timer, so that
+ // processEvents() gets a chance to re-evaluate the state of things.
+ interrupt();
+ }
emit aboutToBlock();
break;
case kCFRunLoopAfterWaiting:
emit awake();
break;
+#if DEBUG_EVENT_DISPATCHER
+ case kCFRunLoopEntry:
+ case kCFRunLoopBeforeTimers:
+ case kCFRunLoopBeforeSources:
+ case kCFRunLoopExit:
+ break;
+#endif
default:
Q_UNREACHABLE();
}
}
-bool QEventDispatcherCoreFoundation::processEvents(QEventLoop::ProcessEventsFlags flags)
+bool QEventDispatcherCoreFoundation::hasPendingEvents()
{
- m_interrupted = false;
- bool eventsProcessed = false;
-
- // The documentation states that this signal is emitted after the event
- // loop returns from a function that could block, which is not the case
- // here, but all the other event dispatchers emit awake at the start of
- // processEvents, and the QEventLoop auto-test has an explicit check for
- // this behavior, so we assume it's for a good reason and do it as well.
- emit awake();
-
- bool excludeUserEvents = flags & QEventLoop::ExcludeUserInputEvents;
- bool execFlagSet = (flags & QEventLoop::DialogExec) || (flags & QEventLoop::EventLoopExec);
- bool useExecMode = execFlagSet && !excludeUserEvents;
-
- SInt32 result;
+ qDebug() << __FUNCTION__ << "not implemented";
+ return false;
+}
- if (useExecMode) {
- while (!m_interrupted) {
- // Run a single pass on the runloop to unblock it
- result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, kCFTimeIntervalMinimum, true);
+void QEventDispatcherCoreFoundation::wakeUp()
+{
+ if (m_processEvents.processedPostedEvents && !(m_processEvents.flags & QEventLoop::EventLoopExec)) {
+ // A manual processEvents call should only result in processing the events posted
+ // up until then. Any newly posted events as result of processing existing posted
+ // events should be handled in the next call to processEvents(). Since we're using
+ // a run-loop source to process our posted events we need to prevent it from being
+ // signaled as a result of posting new events, otherwise we end up in an infinite
+ // loop. We do however need to signal the source at some point, so that the newly
+ // posted event gets processed on the next processEvents() call, so we flag the
+ // need to do a deferred wake-up.
+ m_processEvents.deferredWakeUp = true;
+ qEventDispatcherDebug() << "Already processed posted events, deferring wakeUp";
+ return;
+ }
- // Run the default runloop until interrupted (by Qt or UIKit)
- if (result != kCFRunLoopRunFinished)
- result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, kCFTimeIntervalDistantFuture, false);
+ m_postedEventsRunLoopSource.signal();
+ CFRunLoopWakeUp(CFRunLoopGetMain());
- // App has quit or Qt has interrupted?
- if (result == kCFRunLoopRunFinished || m_interrupted)
- break;
+ qEventDispatcherDebug() << "Signaled posted event run-loop source";
+}
- // Runloop was interrupted by UIKit?
- if (result == kCFRunLoopRunStopped && !m_interrupted) {
- // Run runloop in UI tracking mode
- if (CFRunLoopRunInMode((CFStringRef) UITrackingRunLoopMode,
- kCFTimeIntervalDistantFuture, false) == kCFRunLoopRunFinished)
- break;
- }
- }
- eventsProcessed = true;
- } else {
- if (!(flags & QEventLoop::WaitForMoreEvents))
- wakeUp();
-
- // Run runloop in default mode
- result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, kCFTimeIntervalDistantFuture, true);
- if (result != kCFRunLoopRunFinished) {
- // Run runloop in UI tracking mode
- CFRunLoopRunInMode((CFStringRef) UITrackingRunLoopMode, kCFTimeIntervalDistantFuture, false);
- }
- eventsProcessed = (result == kCFRunLoopRunHandledSource);
- }
- return eventsProcessed;
+void QEventDispatcherCoreFoundation::interrupt()
+{
+ qEventDispatcherDebug() << "Marking current processEvent as interrupted";
+ m_processEvents.wasInterrupted = true;
+ CFRunLoopStop(CFRunLoopGetMain());
}
-bool QEventDispatcherCoreFoundation::hasPendingEvents()
+void QEventDispatcherCoreFoundation::flush()
{
- qDebug() << __FUNCTION__ << "not implemented";
- return false;
+ // X11 only.
}
+#pragma mark - Socket notifiers
+
void QEventDispatcherCoreFoundation::registerSocketNotifier(QSocketNotifier *notifier)
{
m_cfSocketNotifier.registerSocketNotifier(notifier);
@@ -257,97 +444,113 @@ void QEventDispatcherCoreFoundation::unregisterSocketNotifier(QSocketNotifier *n
m_cfSocketNotifier.unregisterSocketNotifier(notifier);
}
-void QEventDispatcherCoreFoundation::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *obj)
+#pragma mark - Timers
+
+void QEventDispatcherCoreFoundation::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *object)
{
-#ifndef QT_NO_DEBUG
- if (timerId < 1 || interval < 0 || !obj) {
- qWarning("QEventDispatcherCoreFoundation::registerTimer: invalid arguments");
- return;
- } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
- qWarning("QEventDispatcherCoreFoundation: timers cannot be started from another thread");
- return;
- }
-#endif
+ qEventDispatcherDebug() << "id = " << timerId << ", interval = " << interval
+ << ", type = " << timerType << ", object = " << object;
- m_timerInfoList.registerTimer(timerId, interval, timerType, obj);
- maybeStartCFRunLoopTimer();
+ Q_ASSERT(timerId > 0 && interval >= 0 && object);
+ Q_ASSERT(object->thread() == thread() && thread() == QThread::currentThread());
+
+ m_timerInfoList.registerTimer(timerId, interval, timerType, object);
+ updateTimers();
}
bool QEventDispatcherCoreFoundation::unregisterTimer(int timerId)
{
-#ifndef QT_NO_DEBUG
- if (timerId < 1) {
- qWarning("QEventDispatcherCoreFoundation::unregisterTimer: invalid argument");
- return false;
- } else if (thread() != QThread::currentThread()) {
- qWarning("QObject::killTimer: timers cannot be stopped from another thread");
- return false;
- }
-#endif
+ Q_ASSERT(timerId > 0);
+ Q_ASSERT(thread() == QThread::currentThread());
bool returnValue = m_timerInfoList.unregisterTimer(timerId);
- m_timerInfoList.isEmpty() ? maybeStopCFRunLoopTimer() : maybeStartCFRunLoopTimer();
+
+ qEventDispatcherDebug() << "id = " << timerId << ", timers left: " << m_timerInfoList.size();
+
+ updateTimers();
return returnValue;
}
bool QEventDispatcherCoreFoundation::unregisterTimers(QObject *object)
{
-#ifndef QT_NO_DEBUG
- if (!object) {
- qWarning("QEventDispatcherCoreFoundation::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;
- }
-#endif
+ Q_ASSERT(object && object->thread() == thread() && thread() == QThread::currentThread());
bool returnValue = m_timerInfoList.unregisterTimers(object);
- m_timerInfoList.isEmpty() ? maybeStopCFRunLoopTimer() : maybeStartCFRunLoopTimer();
+
+ qEventDispatcherDebug() << "object = " << object << ", timers left: " << m_timerInfoList.size();
+
+ updateTimers();
return returnValue;
}
QList<QAbstractEventDispatcher::TimerInfo> QEventDispatcherCoreFoundation::registeredTimers(QObject *object) const
{
-#ifndef QT_NO_DEBUG
- if (!object) {
- qWarning("QEventDispatcherCoreFoundation:registeredTimers: invalid argument");
- return QList<TimerInfo>();
- }
-#endif
-
+ Q_ASSERT(object);
return m_timerInfoList.registeredTimers(object);
}
int QEventDispatcherCoreFoundation::remainingTime(int timerId)
{
-#ifndef QT_NO_DEBUG
- if (timerId < 1) {
- qWarning("QEventDispatcherCoreFoundation::remainingTime: invalid argument");
- return -1;
- }
-#endif
-
+ Q_ASSERT(timerId > 0);
return m_timerInfoList.timerRemainingTime(timerId);
}
-void QEventDispatcherCoreFoundation::wakeUp()
+static double timespecToSeconds(const timespec &spec)
{
- m_postedEventsRunLoopSource.signal();
- CFRunLoopWakeUp(CFRunLoopGetMain());
+ static double nanosecondsPerSecond = 1.0 * 1000 * 1000 * 1000;
+ return spec.tv_sec + (spec.tv_nsec / nanosecondsPerSecond);
}
-void QEventDispatcherCoreFoundation::interrupt()
+void QEventDispatcherCoreFoundation::updateTimers()
{
- // Stop the runloop, which will cause processEvents() to exit
- m_interrupted = true;
- CFRunLoopStop(CFRunLoopGetMain());
+ if (m_timerInfoList.size() > 0) {
+ // We have Qt timers registered, so create or reschedule CF timer to match
+
+ timespec tv = { -1, -1 };
+ CFAbsoluteTime timeToFire = m_timerInfoList.timerWait(tv) ?
+ // We have a timer ready to fire right now, or some time in the future
+ CFAbsoluteTimeGetCurrent() + timespecToSeconds(tv)
+ // We have timers, but they are all currently blocked by callbacks
+ : kCFTimeIntervalDistantFuture;
+
+ if (!m_runLoopTimer) {
+ m_runLoopTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault,
+ timeToFire, kCFTimeIntervalDistantFuture, 0, 0, ^(CFRunLoopTimerRef timer) {
+ processTimers(timer);
+ });
+
+ CFRunLoopAddTimer(CFRunLoopGetMain(), m_runLoopTimer, kCFRunLoopCommonModes);
+ qEventDispatcherDebug() << "Created new CFRunLoopTimer " << m_runLoopTimer;
+
+ } else {
+ CFRunLoopTimerSetNextFireDate(m_runLoopTimer, timeToFire);
+ qEventDispatcherDebug() << "Re-scheduled CFRunLoopTimer " << m_runLoopTimer;
+ }
+
+ m_overdueTimerScheduled = !timespecToSeconds(tv);
+
+ qEventDispatcherDebug() << "Next timeout in " << tv << " seconds";
+
+ } else {
+ // No Qt timers are registered, so make sure we're not running any CF timers
+ invalidateTimer();
+
+ m_overdueTimerScheduled = false;
+ }
}
-void QEventDispatcherCoreFoundation::flush()
+void QEventDispatcherCoreFoundation::invalidateTimer()
{
- // X11 only.
+ if (!m_runLoopTimer || (m_runLoopTimer == m_blockedRunLoopTimer))
+ return;
+
+ CFRunLoopTimerInvalidate(m_runLoopTimer);
+ qEventDispatcherDebug() << "Invalidated CFRunLoopTimer " << m_runLoopTimer;
+
+ CFRelease(m_runLoopTimer);
+ m_runLoopTimer = 0;
}
-QT_END_NAMESPACE
+#include "qeventdispatcher_cf.moc"
+QT_END_NAMESPACE
diff --git a/src/platformsupport/eventdispatchers/qeventdispatcher_cf_p.h b/src/platformsupport/eventdispatchers/qeventdispatcher_cf_p.h
index e23b8f0ece..bd62927f09 100644
--- a/src/platformsupport/eventdispatchers/qeventdispatcher_cf_p.h
+++ b/src/platformsupport/eventdispatchers/qeventdispatcher_cf_p.h
@@ -76,8 +76,11 @@
#ifndef QEVENTDISPATCHER_CF_P_H
#define QEVENTDISPATCHER_CF_P_H
+#define DEBUG_EVENT_DISPATCHER 0
+
#include <QtCore/qabstracteventdispatcher.h>
#include <QtCore/private/qtimerinfo_unix_p.h>
+#include <QtCore/qdebug.h>
#include <QtPlatformSupport/private/qcfsocketnotifier_p.h>
#include <CoreFoundation/CoreFoundation.h>
@@ -200,29 +203,55 @@ public:
void flush();
private:
- bool m_interrupted;
-
RunLoopSource<> m_postedEventsRunLoopSource;
- RunLoopSource<> m_blockingTimerRunLoopSource;
-
- RunLoopObserver<> m_awakeAndBlockObserver;
+ RunLoopObserver<> m_runLoopActivityObserver;
QTimerInfoList m_timerInfoList;
- CFRunLoopTimerRef m_runLoopTimerRef;
+ CFRunLoopTimerRef m_runLoopTimer;
+ CFRunLoopTimerRef m_blockedRunLoopTimer;
+ bool m_overdueTimerScheduled;
QCFSocketNotifier m_cfSocketNotifier;
- void processPostedEvents();
- void processTimers();
+ struct ProcessEventsState
+ {
+ ProcessEventsState(QEventLoop::ProcessEventsFlags f)
+ : flags(f), wasInterrupted(false)
+ , processedPostedEvents(false), processedTimers(false)
+ , deferredWakeUp(false), deferredUpdateTimers(false) {}
- void maybeStartCFRunLoopTimer();
- void maybeStopCFRunLoopTimer();
+ QEventLoop::ProcessEventsFlags flags;
+ bool wasInterrupted;
+ bool processedPostedEvents;
+ bool processedTimers;
+ bool deferredWakeUp;
+ bool deferredUpdateTimers;
+ };
+
+ ProcessEventsState m_processEvents;
+
+ void processPostedEvents();
+ void processTimers(CFRunLoopTimerRef);
void handleRunLoopActivity(CFRunLoopActivity activity);
- static void nonBlockingTimerRunLoopCallback(CFRunLoopTimerRef, void *info);
+ void updateTimers();
+ void invalidateTimer();
};
QT_END_NAMESPACE
+#if DEBUG_EVENT_DISPATCHER
+extern uint g_eventDispatcherIndentationLevel;
+#define qEventDispatcherDebug() qDebug().nospace() \
+ << qPrintable(QString(QLatin1String("| ")).repeated(g_eventDispatcherIndentationLevel)) \
+ << __FUNCTION__ << "(): "
+#define qIndent() ++g_eventDispatcherIndentationLevel
+#define qUnIndent() --g_eventDispatcherIndentationLevel
+#else
+#define qEventDispatcherDebug() QT_NO_QDEBUG_MACRO()
+#define qIndent()
+#define qUnIndent()
+#endif
+
#endif // QEVENTDISPATCHER_CF_P_H