Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qeventdispatcher_cf_p.h"
++
++ #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/qthread_p.h>
++
++ #include <limits>
++
++ #ifdef Q_OS_MACOS
++ # include <AppKit/NSApplication.h>
++ #elif defined(Q_OS_WATCHOS)
++ # include <WatchKit/WatchKit.h>
++ #else
++ # include <UIKit/UIApplication.h>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++ namespace QtPrivate {
++ Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher");
++ Q_LOGGING_CATEGORY(lcEventDispatcherTimers, "qt.eventdispatcher.timers");
++ }
++ using namespace QtPrivate;
++ QT_END_NAMESPACE
++
++ QT_USE_NAMESPACE
++
++ /*
++ During scroll view panning, and possibly other gestures, UIKit will
++ request a switch to UITrackingRunLoopMode via GSEventPushRunLoopMode,
++ which records the new runloop mode and stops the current runloop.
++
++ Unfortunately the runloop mode is just stored on an internal stack, used
++ when UIKit itself is running the runloop, and is not available through e.g.
++ CFRunLoopCopyCurrentMode, which only knows about the current running
++ runloop mode, not the requested future runloop mode.
++
++ To ensure that we pick up this new runloop mode and use it when calling
++ CFRunLoopRunInMode from processEvents, we listen for the notification
++ emitted by [UIApplication pushRunLoopMode:requester:].
++
++ Without this workaround we end up always running in the default runloop
++ mode, resulting in missing momentum-phases in UIScrollViews such as the
++ emoji keyboard.
++ */
++ @interface QT_MANGLE_NAMESPACE(RunLoopModeTracker) : NSObject
++ @end
++
++ QT_NAMESPACE_ALIAS_OBJC_CLASS(RunLoopModeTracker);
++
++ @implementation QT_MANGLE_NAMESPACE(RunLoopModeTracker) {
++ QStack<CFStringRef> m_runLoopModes;
++ }
++
++ - (instancetype)init
++ {
++ if ((self = [super init])) {
++ m_runLoopModes.push(kCFRunLoopDefaultMode);
++
++ #if !defined(Q_OS_WATCHOS)
++ if (!qt_apple_isApplicationExtension()) {
++ [[NSNotificationCenter defaultCenter]
++ addObserver:self selector:@selector(receivedNotification:)
++ name:nil object:qt_apple_sharedApplication()];
++ }
++ #endif
++ }
++
++ return self;
++ }
++
++ - (void)dealloc
++ {
++ [NSNotificationCenter.defaultCenter removeObserver:self];
++
++ [super dealloc];
++ }
++
++ static CFStringRef runLoopMode(NSDictionary *dictionary)
++ {
++ for (NSString *key in dictionary) {
++ if (CFStringHasSuffix((CFStringRef)key, CFSTR("RunLoopMode")))
++ return (CFStringRef)dictionary[key];
++ }
++
++ return nil;
++ }
++
++ - (void)receivedNotification:(NSNotification *)notification
++ {
++ if (CFStringHasSuffix((CFStringRef)notification.name, CFSTR("RunLoopModePushNotification"))) {
++ if (CFStringRef mode = runLoopMode(notification.userInfo))
++ m_runLoopModes.push(mode);
++ else
++ qCWarning(lcEventDispatcher) << "Encountered run loop push notification without run loop mode!";
++
++ } else if (CFStringHasSuffix((CFStringRef)notification.name, CFSTR("RunLoopModePopNotification"))) {
++ CFStringRef mode = runLoopMode(notification.userInfo);
++ if (CFStringCompare(mode, self.currentMode, 0) == kCFCompareEqualTo)
++ m_runLoopModes.pop();
++ else
++ qCWarning(lcEventDispatcher) << "Tried to pop run loop mode"
++ << qPrintable(QString::fromCFString(mode)) << "that was never pushed!";
++
++ Q_ASSERT(m_runLoopModes.size() >= 1);
++ }
++ }
++
++ - (CFStringRef)currentMode
++ {
++ return m_runLoopModes.top();
++ }
++
++ @end
++
++ QT_BEGIN_NAMESPACE
++
++ class RunLoopDebugger : public QObject
++ {
++ Q_OBJECT
++
++ 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)
++ };
++ Q_ENUM(Activity)
++
++ enum Result {
++ Q_MIRROR_ENUM(kCFRunLoopRunFinished),
++ Q_MIRROR_ENUM(kCFRunLoopRunStopped),
++ Q_MIRROR_ENUM(kCFRunLoopRunTimedOut),
++ Q_MIRROR_ENUM(kCFRunLoopRunHandledSource)
++ };
++ Q_ENUM(Result)
++ };
++
++ #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 << Qt::reset;
++ return s;
++ }
++
++ static const CFTimeInterval kCFTimeIntervalMinimum = 0;
++ static const CFTimeInterval kCFTimeIntervalDistantFuture = std::numeric_limits<CFTimeInterval>::max();
++
++ #pragma mark - Class definition
++
++ QEventDispatcherCoreFoundation::QEventDispatcherCoreFoundation(QObject *parent)
++ : QAbstractEventDispatcherV2(parent)
++ , m_processEvents(QEventLoop::EventLoopExec)
++ , m_postedEventsRunLoopSource(this, &QEventDispatcherCoreFoundation::processPostedEvents)
++ , m_runLoopActivityObserver(this, &QEventDispatcherCoreFoundation::handleRunLoopActivity, kCFRunLoopAllActivities)
++ , m_runLoopModeTracker([[RunLoopModeTracker alloc] init])
++ , m_runLoopTimer(0)
++ , m_blockedRunLoopTimer(0)
++ , m_overdueTimerScheduled(false)
++ {
++ }
++
++ void QEventDispatcherCoreFoundation::startingUp()
++ {
++ // The following code must run on the event dispatcher thread, so that
++ // CFRunLoopGetCurrent() returns the correct run loop.
++ Q_ASSERT(QThread::currentThread() == thread());
++
++ m_runLoop = QCFType<CFRunLoopRef>::constructFromGet(CFRunLoopGetCurrent());
++ m_cfSocketNotifier.setHostEventDispatcher(this);
++ m_postedEventsRunLoopSource.addToMode(kCFRunLoopCommonModes);
++ m_runLoopActivityObserver.addToMode(kCFRunLoopCommonModes);
++ }
++
++ QEventDispatcherCoreFoundation::~QEventDispatcherCoreFoundation()
++ {
++ invalidateTimer();
++ m_timerInfoList.clearTimers();
++
++ m_cfSocketNotifier.removeSocketNotifiers();
++ }
++
++ QEventLoop *QEventDispatcherCoreFoundation::currentEventLoop() const
++ {
++ QEventLoop *eventLoop = QThreadData::current()->eventLoops.top();
++ Q_ASSERT(eventLoop);
++ return eventLoop;
++ }
++
++ /*!
++ \internal
++ Processes all pending events that match \a flags until there are no
++ more events to process. Returns \c true if pending events were handled;
++ otherwise returns \c 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.
++ */
++ bool QEventDispatcherCoreFoundation::processEvents(QEventLoop::ProcessEventsFlags flags)
++ {
++ QT_APPLE_SCOPED_LOG_ACTIVITY(lcEventDispatcher().isDebugEnabled(), "processEvents");
++
++ bool eventsProcessed = false;
++
++ if (flags & (QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers))
++ qCWarning(lcEventDispatcher) << "processEvents() flags" << flags << "not supported on iOS";
++
++ qCDebug(lcEventDispatcher) << "Processing events with flags" << flags;
++
++ if (m_blockedRunLoopTimer) {
++ Q_ASSERT(m_blockedRunLoopTimer == m_runLoopTimer);
++
++ qCDebug(lcEventDispatcher) << "Recursing from blocked timer" << m_blockedRunLoopTimer;
++ m_runLoopTimer = 0; // Unset current timer to force creation of new timer
++ updateTimers();
++ }
++
++ if (m_processEvents.deferredWakeUp) {
++ // We may be processing events recursively 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;
++
++ qCDebug(lcEventDispatcher) << "Processed deferred wake-up";
++ }
++
++ // 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();
++
++ ProcessEventsState previousState = m_processEvents;
++ m_processEvents = ProcessEventsState(flags);
++
++ bool returnAfterSingleSourceHandled = !(m_processEvents.flags & QEventLoop::EventLoopExec);
++
++ Q_FOREVER {
++ CFStringRef mode = [m_runLoopModeTracker currentMode];
++
++ CFTimeInterval duration = (m_processEvents.flags & QEventLoop::WaitForMoreEvents) ?
++ kCFTimeIntervalDistantFuture : kCFTimeIntervalMinimum;
++
++ qCDebug(lcEventDispatcher) << "Calling CFRunLoopRunInMode =" << qPrintable(QString::fromCFString(mode))
++ << "for" << duration << "ms, processing single source =" << returnAfterSingleSourceHandled;
++
++ SInt32 result = CFRunLoopRunInMode(mode, duration, returnAfterSingleSourceHandled);
++
++ qCDebug(lcEventDispatcher) << "result =" << qPrintableResult(result);
++
++ eventsProcessed |= (result == kCFRunLoopRunHandledSource
++ || m_processEvents.processedPostedEvents
++ || m_processEvents.processedTimers);
++
++ 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.
++
++ if (!currentEventLoop()->isRunning()) {
++ qCDebug(lcEventDispatcher) << "Top level event loop was exited";
++ break;
++ } else {
++ qCDebug(lcEventDispatcher) << "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.
++ qCDebug(lcEventDispatcher) << "Top level processEvents was interrupted";
++ break;
++ }
++ }
++
++ 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 {
++ // 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 &= ~int(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.
++ qCDebug(lcEventDispatcher) << "Manually processing timers due to overdue timer";
++ processTimers(0);
++ eventsProcessed = true;
++ }
++ }
++
++ break;
++ }
++
++ if (m_blockedRunLoopTimer) {
++ invalidateTimer();
++ m_runLoopTimer = m_blockedRunLoopTimer;
++ }
++
++ if (m_processEvents.deferredUpdateTimers)
++ updateTimers();
++
++ if (m_processEvents.deferredWakeUp) {
++ m_postedEventsRunLoopSource.signal();
++ qCDebug(lcEventDispatcher) << "Processed deferred wake-up";
++ }
++
++ bool wasInterrupted = m_processEvents.wasInterrupted;
++
++ // Restore state of previous processEvents() call
++ m_processEvents = previousState;
++
++ 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.
++ qCDebug(lcEventDispatcher) << "Forwarding interrupt in case of nested processEvents";
++ interrupt();
++ }
++
++ qCDebug(lcEventDispatcher) << "Returning with eventsProcessed =" << eventsProcessed;
++
++ return eventsProcessed;
++ }
++
++ bool QEventDispatcherCoreFoundation::processPostedEvents()
++ {
++ QT_APPLE_SCOPED_LOG_ACTIVITY(lcEventDispatcher().isDebugEnabled(), "processPostedEvents");
++
++ if (m_processEvents.processedPostedEvents && !(m_processEvents.flags & QEventLoop::EventLoopExec)) {
++ qCDebug(lcEventDispatcher) << "Already processed events this pass";
++ return false;
++ }
++
++ m_processEvents.processedPostedEvents = true;
++
++ qCDebug(lcEventDispatcher) << "Sending posted events for"
++ << QEventLoop::ProcessEventsFlags(m_processEvents.flags.loadRelaxed());
++ QCoreApplication::sendPostedEvents();
++
++ return true;
++ }
++
++ void QEventDispatcherCoreFoundation::processTimers(CFRunLoopTimerRef timer)
++ {
++ QT_APPLE_SCOPED_LOG_ACTIVITY(lcEventDispatcher().isDebugEnabled(), "processTimers");
++
++ if (m_processEvents.processedTimers && !(m_processEvents.flags & QEventLoop::EventLoopExec)) {
++ qCDebug(lcEventDispatcher) << "Already processed timers this pass";
++ m_processEvents.deferredUpdateTimers = true;
++ return;
++ }
++
++ qCDebug(lcEventDispatcher) << "CFRunLoopTimer" << timer << "fired, activating Qt timers";
++
++ // 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();
++ m_blockedRunLoopTimer = previouslyBlockedRunLoopTimer;
++ m_processEvents.processedTimers = true;
++
++ // Now that the timer source is unblocked we may need to schedule it again
++ updateTimers();
++ }
++
++ Q_STATIC_LOGGING_CATEGORY(lcEventDispatcherActivity, "qt.eventdispatcher.activity")
++
++ void QEventDispatcherCoreFoundation::handleRunLoopActivity(CFRunLoopActivity activity)
++ {
++ qCDebug(lcEventDispatcherActivity) << "Runloop entered activity" << 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;
++ case kCFRunLoopEntry:
++ case kCFRunLoopBeforeTimers:
++ case kCFRunLoopBeforeSources:
++ case kCFRunLoopExit:
++ break;
++ default:
++ Q_UNREACHABLE();
++ }
++ }
++
++ 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;
++ qCDebug(lcEventDispatcher) << "Already processed posted events, deferring wakeUp";
++ return;
++ }
++
++ m_postedEventsRunLoopSource.signal();
++ if (m_runLoop)
++ CFRunLoopWakeUp(m_runLoop);
++
++ qCDebug(lcEventDispatcher) << "Signaled posted event run-loop source";
++ }
++
++ void QEventDispatcherCoreFoundation::interrupt()
++ {
++ qCDebug(lcEventDispatcher) << "Marking current processEvent as interrupted";
++ m_processEvents.wasInterrupted = true;
++ CFRunLoopStop(m_runLoop);
++ }
++
++ #pragma mark - Socket notifiers
++
++ void QEventDispatcherCoreFoundation::registerSocketNotifier(QSocketNotifier *notifier)
++ {
++ m_cfSocketNotifier.registerSocketNotifier(notifier);
++ }
++
++ void QEventDispatcherCoreFoundation::unregisterSocketNotifier(QSocketNotifier *notifier)
++ {
++ m_cfSocketNotifier.unregisterSocketNotifier(notifier);
++ }
++
++ #pragma mark - Timers
++
++ void QEventDispatcherCoreFoundation::registerTimer(Qt::TimerId timerId, Duration interval,
++ Qt::TimerType timerType, QObject *object)
++ {
++ qCDebug(lcEventDispatcherTimers) << "Registering timer with id =" << int(timerId) << "interval =" << interval
++ << "type =" << timerType << "object =" << object;
++
++ Q_ASSERT(qToUnderlying(timerId) > 0 && interval.count() >= 0 && object);
++ Q_ASSERT(object->thread() == thread() && thread() == QThread::currentThread());
++
++ m_timerInfoList.registerTimer(timerId, interval, timerType, object);
++ updateTimers();
++ }
++
++ bool QEventDispatcherCoreFoundation::unregisterTimer(Qt::TimerId timerId)
++ {
++ Q_ASSERT(qToUnderlying(timerId) > 0);
++ Q_ASSERT(thread() == QThread::currentThread());
++
++ bool returnValue = m_timerInfoList.unregisterTimer(timerId);
++
++ qCDebug(lcEventDispatcherTimers) << "Unegistered timer with id =" << qToUnderlying(timerId)
++ << "Timers left:" << m_timerInfoList.size();
++
++ updateTimers();
++ return returnValue;
++ }
++
++ bool QEventDispatcherCoreFoundation::unregisterTimers(QObject *object)
++ {
++ Q_ASSERT(object && object->thread() == thread() && thread() == QThread::currentThread());
++
++ bool returnValue = m_timerInfoList.unregisterTimers(object);
++
++ qCDebug(lcEventDispatcherTimers) << "Unegistered timers for object =" << object << "Timers left:" << m_timerInfoList.size();
++
++ updateTimers();
++ return returnValue;
++ }
++
++ QList<QAbstractEventDispatcher::TimerInfoV2>
++ QEventDispatcherCoreFoundation::timersForObject(QObject *object) const
++ {
++ Q_ASSERT(object);
++ return m_timerInfoList.registeredTimers(object);
++ }
++
++ QEventDispatcherCoreFoundation::Duration
++ QEventDispatcherCoreFoundation::remainingTime(Qt::TimerId timerId) const
++ {
++ Q_ASSERT(qToUnderlying(timerId) > 0);
++ return m_timerInfoList.remainingDuration(timerId);
++ }
++
++ void QEventDispatcherCoreFoundation::updateTimers()
++ {
++ if (m_timerInfoList.size() > 0) {
++ // We have Qt timers registered, so create or reschedule CF timer to match
++
++ using namespace std::chrono_literals;
++ using DoubleSeconds = std::chrono::duration<double, std::ratio<1>>;
++
++ CFAbsoluteTime timeToFire;
++ auto opt = m_timerInfoList.timerWait();
++ DoubleSeconds secs{};
++ if (opt) {
++ // We have a timer ready to fire right now, or some time in the future
++ secs = DoubleSeconds{*opt};
++ timeToFire = CFAbsoluteTimeGetCurrent() + secs.count();
++ } else {
++ // We have timers, but they are all currently blocked by callbacks
++ timeToFire = kCFTimeIntervalDistantFuture;
++ }
++
++ if (!m_runLoopTimer) {
++ m_runLoopTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault,
++ timeToFire, kCFTimeIntervalDistantFuture, 0, 0, ^(CFRunLoopTimerRef timer) {
++ processTimers(timer);
++ });
++
++ CFRunLoopAddTimer(m_runLoop, m_runLoopTimer, kCFRunLoopCommonModes);
++ qCDebug(lcEventDispatcherTimers) << "Created new CFRunLoopTimer" << m_runLoopTimer;
++
++ } else {
++ CFRunLoopTimerSetNextFireDate(m_runLoopTimer, timeToFire);
++ qCDebug(lcEventDispatcherTimers) << "Re-scheduled CFRunLoopTimer" << m_runLoopTimer;
++ }
++
++ m_overdueTimerScheduled = secs > 0s;
++
++ qCDebug(lcEventDispatcherTimers) << "Next timeout in" << secs;
++
++ } else {
++ // No Qt timers are registered, so make sure we're not running any CF timers
++ invalidateTimer();
++
++ m_overdueTimerScheduled = false;
++ }
++ }
++
++ void QEventDispatcherCoreFoundation::invalidateTimer()
++ {
++ if (!m_runLoopTimer || (m_runLoopTimer == m_blockedRunLoopTimer))
++ return;
++
++ CFRunLoopTimerInvalidate(m_runLoopTimer);
++ qCDebug(lcEventDispatcherTimers) << "Invalidated CFRunLoopTimer" << m_runLoopTimer;
++
++ CFRelease(m_runLoopTimer);
++ m_runLoopTimer = 0;
++ }
++
++ QT_END_NAMESPACE
++
++ #include "qeventdispatcher_cf.moc"
++ #include "moc_qeventdispatcher_cf_p.cpp"