1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qeventdispatcher_cf_p.h"
41
42#include <QtCore/qdebug.h>
43#include <QtCore/qmetaobject.h>
44#include <QtCore/qthread.h>
45#include <QtCore/private/qcoreapplication_p.h>
46#include <QtCore/private/qcore_unix_p.h>
47#include <QtCore/private/qthread_p.h>
48
49#include <limits>
50
51#ifdef Q_OS_MACOS
52#  include <AppKit/NSApplication.h>
53#elif defined(Q_OS_WATCHOS)
54#  include <WatchKit/WatchKit.h>
55#else
56#  include <UIKit/UIApplication.h>
57#endif
58
59QT_BEGIN_NAMESPACE
60namespace QtPrivate {
61Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher");
62Q_LOGGING_CATEGORY(lcEventDispatcherTimers, "qt.eventdispatcher.timers");
63}
64using namespace QtPrivate;
65QT_END_NAMESPACE
66
67QT_USE_NAMESPACE
68
69/*
70    During scroll view panning, and possibly other gestures, UIKit will
71    request a switch to UITrackingRunLoopMode via GSEventPushRunLoopMode,
72    which records the new runloop mode and stops the current runloop.
73
74    Unfortunately the runloop mode is just stored on an internal stack, used
75    when UIKit itself is running the runloop, and is not available through e.g.
76    CFRunLoopCopyCurrentMode, which only knows about the current running
77    runloop mode, not the requested future runloop mode.
78
79    To ensure that we pick up this new runloop mode and use it when calling
80    CFRunLoopRunInMode from processEvents, we listen for the notification
81    emitted by [UIApplication pushRunLoopMode:requester:].
82
83    Without this workaround we end up always running in the default runloop
84    mode, resulting in missing momentum-phases in UIScrollViews such as the
85    emoji keyboard.
86*/
87@interface QT_MANGLE_NAMESPACE(RunLoopModeTracker) : NSObject
88@end
89
90QT_NAMESPACE_ALIAS_OBJC_CLASS(RunLoopModeTracker);
91
92@implementation QT_MANGLE_NAMESPACE(RunLoopModeTracker) {
93    QStack<CFStringRef> m_runLoopModes;
94}
95
96- (instancetype)init
97{
98    if ((self = [super init])) {
99        m_runLoopModes.push(kCFRunLoopDefaultMode);
100
101#if !defined(Q_OS_WATCHOS)
102        if (!qt_apple_isApplicationExtension()) {
103            [[NSNotificationCenter defaultCenter]
104                addObserver:self selector:@selector(receivedNotification:)
105                name:nil object:qt_apple_sharedApplication()];
106        }
107#endif
108    }
109
110    return self;
111}
112
113- (void)dealloc
114{
115    [NSNotificationCenter.defaultCenter removeObserver:self];
116
117    [super dealloc];
118}
119
120static CFStringRef runLoopMode(NSDictionary *dictionary)
121{
122    for (NSString *key in dictionary) {
123        if (CFStringHasSuffix((CFStringRef)key, CFSTR("RunLoopMode")))
124            return (CFStringRef)dictionary[key];
125    }
126
127    return nil;
128}
129
130- (void)receivedNotification:(NSNotification *)notification
131{
132     if (CFStringHasSuffix((CFStringRef)notification.name, CFSTR("RunLoopModePushNotification"))) {
133        if (CFStringRef mode = runLoopMode(notification.userInfo))
134            m_runLoopModes.push(mode);
135        else
136            qCWarning(lcEventDispatcher) << "Encountered run loop push notification without run loop mode!";
137
138     } else if (CFStringHasSuffix((CFStringRef)notification.name, CFSTR("RunLoopModePopNotification"))) {
139        CFStringRef mode = runLoopMode(notification.userInfo);
140        if (CFStringCompare(mode, self.currentMode, 0) == kCFCompareEqualTo)
141            m_runLoopModes.pop();
142        else
143            qCWarning(lcEventDispatcher) << "Tried to pop run loop mode"
144                << qPrintable(QString::fromCFString(mode)) << "that was never pushed!";
145
146        Q_ASSERT(m_runLoopModes.size() >= 1);
147     }
148}
149
150- (CFStringRef)currentMode
151{
152    return m_runLoopModes.top();
153}
154
155@end
156
157QT_BEGIN_NAMESPACE
158
159class RunLoopDebugger : public QObject
160{
161    Q_OBJECT
162
163    Q_ENUMS(Activity)
164    Q_ENUMS(Result)
165
166public:
167
168    #define Q_MIRROR_ENUM(name) name = name
169
170    enum Activity {
171        Q_MIRROR_ENUM(kCFRunLoopEntry),
172        Q_MIRROR_ENUM(kCFRunLoopBeforeTimers),
173        Q_MIRROR_ENUM(kCFRunLoopBeforeSources),
174        Q_MIRROR_ENUM(kCFRunLoopBeforeWaiting),
175        Q_MIRROR_ENUM(kCFRunLoopAfterWaiting),
176        Q_MIRROR_ENUM(kCFRunLoopExit)
177    };
178
179    enum Result {
180        Q_MIRROR_ENUM(kCFRunLoopRunFinished),
181        Q_MIRROR_ENUM(kCFRunLoopRunStopped),
182        Q_MIRROR_ENUM(kCFRunLoopRunTimedOut),
183        Q_MIRROR_ENUM(kCFRunLoopRunHandledSource)
184    };
185};
186
187#define Q_ENUM_PRINTER(enumName) \
188    static const char* qPrintable##enumName(int value) \
189    { \
190        return RunLoopDebugger::staticMetaObject.enumerator(RunLoopDebugger::staticMetaObject.indexOfEnumerator(#enumName)).valueToKey(value); \
191    }
192
193Q_ENUM_PRINTER(Activity);
194Q_ENUM_PRINTER(Result);
195
196QDebug operator<<(QDebug s, timespec tv)
197{
198    s << tv.tv_sec << "." << qSetFieldWidth(9) << qSetPadChar(QChar(48)) << tv.tv_nsec << Qt::reset;
199    return s;
200}
201
202static const CFTimeInterval kCFTimeIntervalMinimum = 0;
203static const CFTimeInterval kCFTimeIntervalDistantFuture = std::numeric_limits<CFTimeInterval>::max();
204
205#pragma mark - Class definition
206
207QEventDispatcherCoreFoundation::QEventDispatcherCoreFoundation(QObject *parent)
208    : QAbstractEventDispatcher(parent)
209    , m_processEvents(QEventLoop::EventLoopExec)
210    , m_postedEventsRunLoopSource(this, &QEventDispatcherCoreFoundation::processPostedEvents)
211    , m_runLoopActivityObserver(this, &QEventDispatcherCoreFoundation::handleRunLoopActivity, kCFRunLoopAllActivities)
212    , m_runLoopModeTracker([[RunLoopModeTracker alloc] init])
213    , m_runLoopTimer(0)
214    , m_blockedRunLoopTimer(0)
215    , m_overdueTimerScheduled(false)
216{
217}
218
219void QEventDispatcherCoreFoundation::startingUp()
220{
221    // The following code must run on the event dispatcher thread, so that
222    // CFRunLoopGetCurrent() returns the correct run loop.
223    Q_ASSERT(QThread::currentThread() == thread());
224
225    m_runLoop = QCFType<CFRunLoopRef>::constructFromGet(CFRunLoopGetCurrent());
226    m_cfSocketNotifier.setHostEventDispatcher(this);
227    m_postedEventsRunLoopSource.addToMode(kCFRunLoopCommonModes);
228    m_runLoopActivityObserver.addToMode(kCFRunLoopCommonModes);
229}
230
231QEventDispatcherCoreFoundation::~QEventDispatcherCoreFoundation()
232{
233    invalidateTimer();
234    qDeleteAll(m_timerInfoList);
235
236    m_cfSocketNotifier.removeSocketNotifiers();
237}
238
239QEventLoop *QEventDispatcherCoreFoundation::currentEventLoop() const
240{
241    QEventLoop *eventLoop = QThreadData::current()->eventLoops.top();
242    Q_ASSERT(eventLoop);
243    return eventLoop;
244}
245
246/*!
247    Processes all pending events that match \a flags until there are no
248    more events to process. Returns \c true if pending events were handled;
249    otherwise returns \c false.
250
251    Note:
252
253      - All events are considered equal. This function should process
254        both system/native events (that we may or may not care about),
255        as well as Qt-events (posted events and timers).
256
257      - The function should not return until all queued/available events
258        have been processed. If the WaitForMoreEvents is set, the
259        function should wait only if there were no events ready,
260        and _then_ process all newly queued/available events.
261
262    These notes apply to other function in this class as well, such as
263    hasPendingEvents().
264*/
265bool QEventDispatcherCoreFoundation::processEvents(QEventLoop::ProcessEventsFlags flags)
266{
267    QT_APPLE_SCOPED_LOG_ACTIVITY(lcEventDispatcher().isDebugEnabled(), "processEvents");
268
269    bool eventsProcessed = false;
270
271    if (flags & (QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers))
272        qCWarning(lcEventDispatcher) << "processEvents() flags" << flags << "not supported on iOS";
273
274    qCDebug(lcEventDispatcher) << "Processing events with flags" << flags;
275
276    if (m_blockedRunLoopTimer) {
277        Q_ASSERT(m_blockedRunLoopTimer == m_runLoopTimer);
278
279        qCDebug(lcEventDispatcher) << "Recursing from blocked timer" << m_blockedRunLoopTimer;
280        m_runLoopTimer = 0; // Unset current timer to force creation of new timer
281        updateTimers();
282    }
283
284    if (m_processEvents.deferredWakeUp) {
285        // We may be processing events recursivly as a result of processing a posted event,
286        // in which case we need to signal the run-loop source so that this iteration of
287        // processEvents will take care of the newly posted events.
288        m_postedEventsRunLoopSource.signal();
289        m_processEvents.deferredWakeUp = false;
290
291        qCDebug(lcEventDispatcher) << "Processed deferred wake-up";
292    }
293
294    // The documentation states that this signal is emitted after the event
295    // loop returns from a function that could block, which is not the case
296    // here, but all the other event dispatchers emit awake at the start of
297    // processEvents, and the QEventLoop auto-test has an explicit check for
298    // this behavior, so we assume it's for a good reason and do it as well.
299    emit awake();
300
301    ProcessEventsState previousState = m_processEvents;
302    m_processEvents = ProcessEventsState(flags);
303
304    bool returnAfterSingleSourceHandled = !(m_processEvents.flags & QEventLoop::EventLoopExec);
305
306    Q_FOREVER {
307        CFStringRef mode = [m_runLoopModeTracker currentMode];
308
309        CFTimeInterval duration = (m_processEvents.flags & QEventLoop::WaitForMoreEvents) ?
310            kCFTimeIntervalDistantFuture : kCFTimeIntervalMinimum;
311
312        qCDebug(lcEventDispatcher) << "Calling CFRunLoopRunInMode =" << qPrintable(QString::fromCFString(mode))
313            << "for" << duration << "ms, processing single source =" << returnAfterSingleSourceHandled;
314
315        SInt32 result = CFRunLoopRunInMode(mode, duration, returnAfterSingleSourceHandled);
316
317        qCDebug(lcEventDispatcher) << "result =" << qPrintableResult(result);
318
319        eventsProcessed |= (result == kCFRunLoopRunHandledSource
320                            || m_processEvents.processedPostedEvents
321                            || m_processEvents.processedTimers);
322
323        if (result == kCFRunLoopRunFinished) {
324            // This should only happen at application shutdown, as the main runloop
325            // will presumably always have sources registered.
326            break;
327        } else if (m_processEvents.wasInterrupted) {
328
329            if (m_processEvents.flags & QEventLoop::EventLoopExec) {
330                Q_ASSERT(result == kCFRunLoopRunStopped);
331
332                // The runloop was potentially stopped (interrupted) by us, as a response to
333                // a Qt event loop being asked to exit. We check that the topmost eventloop
334                // is still supposed to keep going and return if not. Note that the runloop
335                // might get stopped as a result of a non-top eventloop being asked to exit,
336                // in which case we continue running the top event loop until that is asked
337                // to exit, and then unwind back to the previous event loop which will break
338                // immediately, since it has already been exited.
339
340                if (!currentEventLoop()->isRunning()) {
341                    qCDebug(lcEventDispatcher) << "Top level event loop was exited";
342                    break;
343                } else {
344                    qCDebug(lcEventDispatcher) << "Top level event loop still running, making another pass";
345                }
346            } else {
347                // We were called manually, through processEvents(), and should stop processing
348                // events, even if we didn't finish processing all the queued events.
349                qCDebug(lcEventDispatcher) << "Top level processEvents was interrupted";
350                break;
351            }
352        }
353
354        if (m_processEvents.flags & QEventLoop::EventLoopExec) {
355            // We were called from QEventLoop's exec(), which blocks until the event
356            // loop is asked to exit by calling processEvents repeatedly. Instead of
357            // re-entering this method again and again from QEventLoop, we can block
358            // here,  one lever closer to CFRunLoopRunInMode, by running the native
359            // event loop again and again until we're interrupted by QEventLoop.
360            continue;
361        } else {
362            // We were called 'manually', through processEvents()
363
364            if (result == kCFRunLoopRunHandledSource) {
365                // We processed one or more sources, but there might still be other
366                // sources that did not get a chance to process events, so we need
367                // to do another pass.
368
369                // But we should only wait for more events the first time
370                m_processEvents.flags &= ~QEventLoop::WaitForMoreEvents;
371                continue;
372
373            } else if (m_overdueTimerScheduled && !m_processEvents.processedTimers) {
374                // CFRunLoopRunInMode does not guarantee that a scheduled timer with a fire
375                // date in the past (overdue) will fire on the next run loop pass. The Qt
376                // APIs on the other hand document eg. zero-interval timers to always be
377                // handled after processing all available window-system events.
378                qCDebug(lcEventDispatcher) << "Manually processing timers due to overdue timer";
379                processTimers(0);
380                eventsProcessed = true;
381            }
382        }
383
384        break;
385    }
386
387    if (m_blockedRunLoopTimer) {
388        invalidateTimer();
389        m_runLoopTimer = m_blockedRunLoopTimer;
390    }
391
392    if (m_processEvents.deferredUpdateTimers)
393        updateTimers();
394
395    if (m_processEvents.deferredWakeUp) {
396        m_postedEventsRunLoopSource.signal();
397        qCDebug(lcEventDispatcher) << "Processed deferred wake-up";
398    }
399
400    bool wasInterrupted = m_processEvents.wasInterrupted;
401
402    // Restore state of previous processEvents() call
403    m_processEvents = previousState;
404
405    if (wasInterrupted) {
406        // The current processEvents run has been interrupted, but there may still be
407        // others below it (eg, in the case of nested event loops). We need to trigger
408        // another interrupt so that the parent processEvents call has a chance to check
409        // if it should continue.
410        qCDebug(lcEventDispatcher) << "Forwarding interrupt in case of nested processEvents";
411        interrupt();
412    }
413
414    qCDebug(lcEventDispatcher) << "Returning with eventsProcessed =" << eventsProcessed;
415
416    return eventsProcessed;
417}
418
419bool QEventDispatcherCoreFoundation::processPostedEvents()
420{
421    QT_APPLE_SCOPED_LOG_ACTIVITY(lcEventDispatcher().isDebugEnabled(), "processPostedEvents");
422
423    if (m_processEvents.processedPostedEvents && !(m_processEvents.flags & QEventLoop::EventLoopExec)) {
424        qCDebug(lcEventDispatcher) << "Already processed events this pass";
425        return false;
426    }
427
428    m_processEvents.processedPostedEvents = true;
429
430    qCDebug(lcEventDispatcher) << "Sending posted events for"
431        << QEventLoop::ProcessEventsFlags(m_processEvents.flags.loadRelaxed());
432    QCoreApplication::sendPostedEvents();
433
434    return true;
435}
436
437void QEventDispatcherCoreFoundation::processTimers(CFRunLoopTimerRef timer)
438{
439    QT_APPLE_SCOPED_LOG_ACTIVITY(lcEventDispatcher().isDebugEnabled(), "processTimers");
440
441    if (m_processEvents.processedTimers && !(m_processEvents.flags & QEventLoop::EventLoopExec)) {
442        qCDebug(lcEventDispatcher) << "Already processed timers this pass";
443        m_processEvents.deferredUpdateTimers = true;
444        return;
445    }
446
447    qCDebug(lcEventDispatcher) << "CFRunLoopTimer" << timer << "fired, activating Qt timers";
448
449    // Activating Qt timers might recurse into processEvents() if a timer-callback
450    // brings up a new event-loop or tries to processes events manually. Although
451    // a CFRunLoop can recurse inside its callbacks, a single CFRunLoopTimer can
452    // not. So, for each recursion into processEvents() from a timer-callback we
453    // need to set up a new timer-source. Instead of doing it preemtivly each
454    // time we activate Qt timers, we set a flag here, and let processEvents()
455    // decide whether or not it needs to bring up a new timer source.
456
457    // We may have multiple recused timers, so keep track of the previous blocked timer
458    CFRunLoopTimerRef previouslyBlockedRunLoopTimer = m_blockedRunLoopTimer;
459
460    m_blockedRunLoopTimer = timer;
461    m_timerInfoList.activateTimers();
462    m_blockedRunLoopTimer = previouslyBlockedRunLoopTimer;
463    m_processEvents.processedTimers = true;
464
465    // Now that the timer source is unblocked we may need to schedule it again
466    updateTimers();
467}
468
469Q_LOGGING_CATEGORY(lcEventDispatcherActivity, "qt.eventdispatcher.activity")
470
471void QEventDispatcherCoreFoundation::handleRunLoopActivity(CFRunLoopActivity activity)
472{
473    qCDebug(lcEventDispatcherActivity) << "Runloop entered activity" << qPrintableActivity(activity);
474
475    switch (activity) {
476    case kCFRunLoopBeforeWaiting:
477        if (m_processEvents.processedTimers
478                && !(m_processEvents.flags & QEventLoop::EventLoopExec)
479                && m_processEvents.flags & QEventLoop::WaitForMoreEvents) {
480            // CoreFoundation does not treat a timer as a reason to exit CFRunLoopRunInMode
481            // when asked to only process a single source, so we risk waiting a long time for
482            // a 'proper' source to fire (typically a system source that we don't control).
483            // To fix this we do an explicit interrupt after processing our timer, so that
484            // processEvents() gets a chance to re-evaluate the state of things.
485            interrupt();
486        }
487        emit aboutToBlock();
488        break;
489    case kCFRunLoopAfterWaiting:
490        emit awake();
491        break;
492    case kCFRunLoopEntry:
493    case kCFRunLoopBeforeTimers:
494    case kCFRunLoopBeforeSources:
495    case kCFRunLoopExit:
496        break;
497    default:
498        Q_UNREACHABLE();
499    }
500}
501
502bool QEventDispatcherCoreFoundation::hasPendingEvents()
503{
504    // There doesn't seem to be any API on iOS to peek into the other sources
505    // to figure out if there are pending non-Qt events. As a workaround, we
506    // assume that if the run-loop is currently blocking and waiting for a
507    // source to signal then there are no system-events pending. If this
508    // function is called from the main thread then the second clause
509    // of the condition will always be true, as the run loop is
510    // never waiting in that case. The function would be more aptly named
511    // 'maybeHasPendingEvents' in our case.
512
513    extern uint qGlobalPostedEventsCount();
514    return qGlobalPostedEventsCount() || !CFRunLoopIsWaiting(m_runLoop);
515}
516
517void QEventDispatcherCoreFoundation::wakeUp()
518{
519    if (m_processEvents.processedPostedEvents && !(m_processEvents.flags & QEventLoop::EventLoopExec)) {
520        // A manual processEvents call should only result in processing the events posted
521        // up until then. Any newly posted events as result of processing existing posted
522        // events should be handled in the next call to processEvents(). Since we're using
523        // a run-loop source to process our posted events we need to prevent it from being
524        // signaled as a result of posting new events, otherwise we end up in an infinite
525        // loop. We do however need to signal the source at some point, so that the newly
526        // posted event gets processed on the next processEvents() call, so we flag the
527        // need to do a deferred wake-up.
528        m_processEvents.deferredWakeUp = true;
529        qCDebug(lcEventDispatcher) << "Already processed posted events, deferring wakeUp";
530        return;
531    }
532
533    m_postedEventsRunLoopSource.signal();
534    if (m_runLoop)
535        CFRunLoopWakeUp(m_runLoop);
536
537    qCDebug(lcEventDispatcher) << "Signaled posted event run-loop source";
538}
539
540void QEventDispatcherCoreFoundation::interrupt()
541{
542    qCDebug(lcEventDispatcher) << "Marking current processEvent as interrupted";
543    m_processEvents.wasInterrupted = true;
544    CFRunLoopStop(m_runLoop);
545}
546
547void QEventDispatcherCoreFoundation::flush()
548{
549    // X11 only.
550}
551
552#pragma mark - Socket notifiers
553
554void QEventDispatcherCoreFoundation::registerSocketNotifier(QSocketNotifier *notifier)
555{
556    m_cfSocketNotifier.registerSocketNotifier(notifier);
557}
558
559void QEventDispatcherCoreFoundation::unregisterSocketNotifier(QSocketNotifier *notifier)
560{
561    m_cfSocketNotifier.unregisterSocketNotifier(notifier);
562}
563
564#pragma mark - Timers
565
566void QEventDispatcherCoreFoundation::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *object)
567{
568    qCDebug(lcEventDispatcherTimers) << "Registering timer with id =" << timerId << "interval =" << interval
569        << "type =" << timerType << "object =" << object;
570
571    Q_ASSERT(timerId > 0 && interval >= 0 && object);
572    Q_ASSERT(object->thread() == thread() && thread() == QThread::currentThread());
573
574    m_timerInfoList.registerTimer(timerId, interval, timerType, object);
575    updateTimers();
576}
577
578bool QEventDispatcherCoreFoundation::unregisterTimer(int timerId)
579{
580    Q_ASSERT(timerId > 0);
581    Q_ASSERT(thread() == QThread::currentThread());
582
583    bool returnValue = m_timerInfoList.unregisterTimer(timerId);
584
585    qCDebug(lcEventDispatcherTimers) << "Unegistered timer with id =" << timerId << "Timers left:" << m_timerInfoList.size();
586
587    updateTimers();
588    return returnValue;
589}
590
591bool QEventDispatcherCoreFoundation::unregisterTimers(QObject *object)
592{
593    Q_ASSERT(object && object->thread() == thread() && thread() == QThread::currentThread());
594
595    bool returnValue = m_timerInfoList.unregisterTimers(object);
596
597    qCDebug(lcEventDispatcherTimers) << "Unegistered timers for object =" << object << "Timers left:" << m_timerInfoList.size();
598
599    updateTimers();
600    return returnValue;
601}
602
603QList<QAbstractEventDispatcher::TimerInfo> QEventDispatcherCoreFoundation::registeredTimers(QObject *object) const
604{
605    Q_ASSERT(object);
606    return m_timerInfoList.registeredTimers(object);
607}
608
609int QEventDispatcherCoreFoundation::remainingTime(int timerId)
610{
611    Q_ASSERT(timerId > 0);
612    return m_timerInfoList.timerRemainingTime(timerId);
613}
614
615static double timespecToSeconds(const timespec &spec)
616{
617    static double nanosecondsPerSecond = 1.0 * 1000 * 1000 * 1000;
618    return spec.tv_sec + (spec.tv_nsec / nanosecondsPerSecond);
619}
620
621void QEventDispatcherCoreFoundation::updateTimers()
622{
623    if (m_timerInfoList.size() > 0) {
624        // We have Qt timers registered, so create or reschedule CF timer to match
625
626        timespec tv = { -1, -1 };
627        CFAbsoluteTime timeToFire = m_timerInfoList.timerWait(tv) ?
628            // We have a timer ready to fire right now, or some time in the future
629            CFAbsoluteTimeGetCurrent() + timespecToSeconds(tv)
630            // We have timers, but they are all currently blocked by callbacks
631            : kCFTimeIntervalDistantFuture;
632
633        if (!m_runLoopTimer) {
634            m_runLoopTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault,
635                timeToFire, kCFTimeIntervalDistantFuture, 0, 0, ^(CFRunLoopTimerRef timer) {
636                processTimers(timer);
637            });
638
639            CFRunLoopAddTimer(m_runLoop, m_runLoopTimer, kCFRunLoopCommonModes);
640            qCDebug(lcEventDispatcherTimers) << "Created new CFRunLoopTimer" << m_runLoopTimer;
641
642        } else {
643            CFRunLoopTimerSetNextFireDate(m_runLoopTimer, timeToFire);
644            qCDebug(lcEventDispatcherTimers) << "Re-scheduled CFRunLoopTimer" << m_runLoopTimer;
645        }
646
647        m_overdueTimerScheduled = !timespecToSeconds(tv);
648
649        qCDebug(lcEventDispatcherTimers) << "Next timeout in" << tv << "seconds";
650
651    } else {
652        // No Qt timers are registered, so make sure we're not running any CF timers
653        invalidateTimer();
654
655        m_overdueTimerScheduled = false;
656    }
657}
658
659void QEventDispatcherCoreFoundation::invalidateTimer()
660{
661    if (!m_runLoopTimer || (m_runLoopTimer == m_blockedRunLoopTimer))
662        return;
663
664    CFRunLoopTimerInvalidate(m_runLoopTimer);
665    qCDebug(lcEventDispatcherTimers) << "Invalidated CFRunLoopTimer" << m_runLoopTimer;
666
667    CFRelease(m_runLoopTimer);
668    m_runLoopTimer = 0;
669}
670
671#include "qeventdispatcher_cf.moc"
672#include "moc_qeventdispatcher_cf_p.cpp"
673
674QT_END_NAMESPACE
675