summaryrefslogtreecommitdiffstats
path: root/src/platformsupport
diff options
context:
space:
mode:
Diffstat (limited to 'src/platformsupport')
-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