summaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
authorBradley T. Hughes <bradley.hughes@nokia.com>2012-04-11 13:08:40 +0200
committerQt by Nokia <qt-info@nokia.com>2012-04-17 10:15:44 +0200
commitbffbfc5b3368abaa5f95b7e4f8342e9202ab065a (patch)
treefcc07c0fde8d798287e3d870a83cd87dc4034f86 /src/plugins
parent2afeb072273bf6977b7e1897a1350cfeefa7d1b5 (diff)
Cocoa: allow timer activation to recurse
CFRunLoopTimer's callback will never recurse. Since we are using one CFRunLoopTimer to drive all Qt timers, we need to work around this by sending all timers via a dedicated CFRunLoopSource (since these callbacks can recurse). 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. Change-Id: I375e46b6cfa0c76db678a1085314d42d8996d062 Reviewed-by: Morten Johan Sørvig <morten.sorvig@nokia.com>
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/platforms/cocoa/qcocoaeventdispatcher.h8
-rw-r--r--src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm72
2 files changed, 57 insertions, 23 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
index a4a1286fab..f286b632fb 100644
--- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
+++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
@@ -116,7 +116,6 @@ public:
explicit QCocoaEventDispatcher(QObject *parent = 0);
~QCocoaEventDispatcher();
-
bool processEvents(QEventLoop::ProcessEventsFlags flags);
bool hasPendingEvents();
@@ -154,9 +153,11 @@ public:
// timer handling
QTimerInfoList timerInfoList;
CFRunLoopTimerRef runLoopTimerRef;
+ CFRunLoopSourceRef activateTimersSourceRef;
void maybeStartCFRunLoopTimer();
void maybeStopCFRunLoopTimer();
- static void activateTimer(CFRunLoopTimerRef, void *info);
+ static void runLoopTimerCallback(CFRunLoopTimerRef, void *info);
+ static void activateTimersSourceCallback(void *info);
// Set 'blockSendPostedEvents' to true if you _really_ need
// to make sure that qt events are not posted while calling
@@ -188,8 +189,7 @@ public:
int lastSerial;
bool interrupt;
- static Boolean postedEventSourceEqualCallback(const void *info1, const void *info2);
- static void postedEventsSourcePerformCallback(void *info);
+ static void postedEventsSourceCallback(void *info);
static void waitingObserverCallback(CFRunLoopObserverRef observer,
CFRunLoopActivity activity, void *info);
static void firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info);
diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
index 77cccac0b4..f2927d32c1 100644
--- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
+++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
@@ -104,12 +104,28 @@ static inline CFRunLoopRef mainRunLoop()
return CFRunLoopGetMain();
}
+static Boolean runLoopSourceEqualCallback(const void *info1, const void *info2)
+{
+ return info1 == info2;
+}
+
/*****************************************************************************
Timers stuff
*****************************************************************************/
/* timer call back */
-void QCocoaEventDispatcherPrivate::activateTimer(CFRunLoopTimerRef, void *info)
+void QCocoaEventDispatcherPrivate::runLoopTimerCallback(CFRunLoopTimerRef, void *info)
+{
+ QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
+ if ((d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
+ // processEvents() was called "manually," ignore this source for now
+ d->maybeCancelWaitForMoreEvents();
+ return;
+ }
+ CFRunLoopSourceSignal(d->activateTimersSourceRef);
+}
+
+void QCocoaEventDispatcherPrivate::activateTimersSourceCallback(void *info)
{
QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
(void) d->timerInfoList.activateTimers();
@@ -145,7 +161,7 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
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
- runLoopTimerRef = CFRunLoopTimerCreate(0, ttf, oneyear, 0, 0, QCocoaEventDispatcherPrivate::activateTimer, &info);
+ runLoopTimerRef = CFRunLoopTimerCreate(0, ttf, oneyear, 0, 0, QCocoaEventDispatcherPrivate::runLoopTimerCallback, &info);
Q_ASSERT(runLoopTimerRef != 0);
CFRunLoopAddTimer(mainRunLoop(), runLoopTimerRef, kCFRunLoopCommonModes);
@@ -513,10 +529,10 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
emit awake();
- bool excludeUserEvents = flags & QEventLoop::ExcludeUserInputEvents;
- bool retVal = false;
uint oldflags = d->processEventsFlags;
d->processEventsFlags = flags;
+ bool excludeUserEvents = d->processEventsFlags & QEventLoop::ExcludeUserInputEvents;
+ bool retVal = false;
forever {
if (d->interrupt)
break;
@@ -544,8 +560,9 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
// Finally, if we are to exclude user input events, we cannot call [NSApp run]
// as we then loose control over which events gets dispatched:
const bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning];
- const bool canExec_Qt = !excludeUserEvents &&
- (flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec) ;
+ const bool canExec_Qt = (!excludeUserEvents
+ && ((d->processEventsFlags & QEventLoop::DialogExec)
+ || (d->processEventsFlags & QEventLoop::EventLoopExec)));
if (canExec_Qt && canExec_3rdParty) {
// We can use exec-mode, meaning that we can stay in a tight loop until
@@ -632,9 +649,11 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
}
} while (!d->interrupt && event != nil);
- if ((flags & QEventLoop::WaitForMoreEvents) == 0) {
- // when called "manually", always send posted events
+ if ((d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
+ // when called "manually", always send posted events and timers
d->processPostedEvents();
+ retVal = d->timerInfoList.activateTimers() > 0 || retVal;
+ d->maybeStartCFRunLoopTimer();
}
// be sure to return true if the posted event source fired
@@ -651,12 +670,12 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
bool canWait = (d->threadData->canWait
&& !retVal
&& !d->interrupt
- && (flags & QEventLoop::WaitForMoreEvents));
+ && (d->processEventsFlags & QEventLoop::WaitForMoreEvents));
if (canWait) {
// INVARIANT: We haven't processed any events yet. And we're told
// to stay inside this function until at least one event is processed.
qt_mac_waitForMoreEvents();
- flags &= ~QEventLoop::WaitForMoreEvents;
+ d->processEventsFlags &= ~QEventLoop::WaitForMoreEvents;
} else {
// Done with event processing for now.
// Leave the function:
@@ -904,15 +923,28 @@ QCocoaEventDispatcher::QCocoaEventDispatcher(QObject *parent)
: QAbstractEventDispatcher(*new QCocoaEventDispatcherPrivate, parent)
{
Q_D(QCocoaEventDispatcher);
+
+ // keep our sources running when modal loops are running
+ CFRunLoopAddCommonMode(mainRunLoop(), (CFStringRef) NSModalPanelRunLoopMode);
+
CFRunLoopSourceContext context;
bzero(&context, sizeof(CFRunLoopSourceContext));
context.info = d;
- context.equal = QCocoaEventDispatcherPrivate::postedEventSourceEqualCallback;
- context.perform = QCocoaEventDispatcherPrivate::postedEventsSourcePerformCallback;
- d->postedEventsSource = CFRunLoopSourceCreate(0, 0, &context);
+ context.equal = runLoopSourceEqualCallback;
+
+ // source used to activate timers
+ context.perform = QCocoaEventDispatcherPrivate::activateTimersSourceCallback;
+ d->activateTimersSourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
+ Q_ASSERT(d->activateTimersSourceRef);
+ CFRunLoopAddSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
+
+ // source used to send posted events
+ context.perform = QCocoaEventDispatcherPrivate::postedEventsSourceCallback;
+ d->postedEventsSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
Q_ASSERT(d->postedEventsSource);
CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
+ // observer to emit aboutToBlock() and awake()
CFRunLoopObserverContext observerContext;
bzero(&observerContext, sizeof(CFRunLoopObserverContext));
observerContext.info = this;
@@ -948,11 +980,6 @@ void QCocoaEventDispatcherPrivate::waitingObserverCallback(CFRunLoopObserverRef,
emit static_cast<QCocoaEventDispatcher*>(info)->awake();
}
-Boolean QCocoaEventDispatcherPrivate::postedEventSourceEqualCallback(const void *info1, const void *info2)
-{
- return info1 == info2;
-}
-
void QCocoaEventDispatcherPrivate::processPostedEvents()
{
if (blockSendPostedEvents) {
@@ -1017,9 +1044,14 @@ void QCocoaEventDispatcherPrivate::firstLoopEntry(CFRunLoopObserverRef ref,
static_cast<QCocoaEventDispatcherPrivate *>(info)->processPostedEvents();
}
-void QCocoaEventDispatcherPrivate::postedEventsSourcePerformCallback(void *info)
+void QCocoaEventDispatcherPrivate::postedEventsSourceCallback(void *info)
{
QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
+ if ((d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
+ // processEvents() was called "manually," ignore this source for now
+ d->maybeCancelWaitForMoreEvents();
+ return;
+ }
d->processPostedEvents();
d->maybeCancelWaitForMoreEvents();
}
@@ -1069,6 +1101,8 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher()
qDeleteAll(d->timerInfoList);
d->maybeStopCFRunLoopTimer();
+ CFRunLoopRemoveSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
+ CFRelease(d->activateTimersSourceRef);
// Remove CFSockets from the runloop.
for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) {