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