diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm | 154 |
1 files changed, 109 insertions, 45 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm index 02722ce5bf..a3bd4a95ca 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm @@ -82,6 +82,7 @@ #include "qmutex.h" #include "qsocketnotifier.h" #include <qplatformwindow_qpa.h> +#include <qplatformnativeinterface_qpa.h> #include "private/qthread_p.h" #include "private/qguiapplication_p.h" #include <qdebug.h> @@ -104,12 +105,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 +162,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 +530,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 +561,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 +650,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 +671,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: @@ -762,26 +782,18 @@ NSModalSession QCocoaEventDispatcherPrivate::currentModalSession() QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; if (!info.window) continue; -// ### port -// if (info.window->testAttribute(Qt::WA_DontShowOnScreen)) -// continue; if (!info.session) { QCocoaAutoReleasePool pool; - NSWindow *window = reinterpret_cast<NSWindow *>(info.window->handle()->winId()); - if (!window) + NSWindow *nswindow = static_cast<NSWindow *>(QGuiApplication::platformNativeInterface()->nativeResourceForWindow("nswindow", info.window)); + if (!nswindow) continue; ensureNSAppInitialized(); QBoolBlocker block1(blockSendPostedEvents, true); - info.nswindow = window; + info.nswindow = nswindow; [(NSWindow*) info.nswindow retain]; - int levelBeforeEnterModal = [window level]; - info.session = [NSApp beginModalSessionForWindow:window]; - // Make sure we don't stack the window lower that it was before - // entering modal, in case it e.g. had the stays-on-top flag set: - if (levelBeforeEnterModal > [window level]) - [window setLevel:levelBeforeEnterModal]; + info.session = [NSApp beginModalSessionForWindow:nswindow]; } currentModalSessionCached = info.session; cleanupModalSessionsNeeded = false; @@ -850,12 +862,14 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions() currentModalSessionCached = info.session; break; } - cocoaModalSessionStack.remove(i); currentModalSessionCached = 0; if (info.session) { + Q_ASSERT(info.nswindow != 0); [NSApp endModalSession:info.session]; [(NSWindow *)info.nswindow release]; } + // remove the info now that we are finished with it + cocoaModalSessionStack.remove(i); } updateChildrenWorksWhenModal(); @@ -864,6 +878,14 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions() void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window) { + // We need to start spinning the modal session. Usually this is done with + // QDialog::exec() for QtWidgets based applications, but for others that + // just call show(), we need to interrupt(). We call this here, before + // setting currentModalSessionCached to zero, so that interrupt() calls + // [NSApp abortModal] if another modal session is currently running + Q_Q(QCocoaEventDispatcher); + q->interrupt(); + // Add a new, empty (null), NSModalSession to the stack. // It will become active the next time QEventDispatcher::processEvents is called. // A QCocoaModalSessionInfo is considered pending to become active if the window pointer @@ -879,6 +901,8 @@ void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window) void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window) { + Q_Q(QCocoaEventDispatcher); + // Mark all sessions attached to window as pending to be stopped. We do this // by setting the window pointer to zero, but leave the session pointer. // We don't tell cocoa to stop any sessions just yet, because cocoa only understands @@ -890,11 +914,14 @@ void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window) if (info.window == window) { info.window = 0; if (i == stackSize-1) { - // The top sessions ended. Interrupt the event dispatcher - // to start spinning the correct session immidiatly: + // The top sessions ended. Interrupt the event dispatcher to + // start spinning the correct session immediately. Like in + // beginModalSession(), we call interrupt() before clearing + // currentModalSessionCached to make sure we stop any currently + // running modal session with [NSApp abortModal] + q->interrupt(); currentModalSessionCached = 0; cleanupModalSessionsNeeded = true; - QCocoaEventDispatcher::instance()->interrupt(); } } } @@ -917,15 +944,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; @@ -961,11 +1001,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) { @@ -1030,9 +1065,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(); } @@ -1061,16 +1101,23 @@ void QCocoaEventDispatcher::interrupt() { Q_D(QCocoaEventDispatcher); d->interrupt = true; - wakeUp(); - - // We do nothing more here than setting d->interrupt = true, and - // poke the event loop if it is sleeping. Actually stopping - // NSApp, or the current modal session, is done inside the send - // posted events callback. We do this to ensure that all current pending - // cocoa events gets delivered before we stop. Otherwise, if we now stop - // the last event loop recursion, cocoa will just drop pending posted - // events on the floor before we get a chance to reestablish a new session. - d->cancelWaitForMoreEvents(); + if (d->currentModalSessionCached) { + // If a modal session is active, abort it so that we can clean it up + // later. We can't use [NSApp stopModal] here, because we do not know + // where the interrupt() came from. + [NSApp abortModal]; + } else { + wakeUp(); + + // We do nothing more here than setting d->interrupt = true, and + // poke the event loop if it is sleeping. Actually stopping + // NSApp, or the current modal session, is done inside the send + // posted events callback. We do this to ensure that all current pending + // cocoa events gets delivered before we stop. Otherwise, if we now stop + // the last event loop recursion, cocoa will just drop pending posted + // events on the floor before we get a chance to reestablish a new session. + d->cancelWaitForMoreEvents(); + } } void QCocoaEventDispatcher::flush() @@ -1082,6 +1129,23 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher() qDeleteAll(d->timerInfoList); d->maybeStopCFRunLoopTimer(); + CFRunLoopRemoveSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes); + CFRelease(d->activateTimersSourceRef); + + // end all modal sessions + for (int i = 0; i < d->cocoaModalSessionStack.count(); ++i) { + QCocoaModalSessionInfo &info = d->cocoaModalSessionStack[i]; + if (info.session) { + [NSApp endModalSession:info.session]; + [(NSWindow *)info.nswindow release]; + } + } + + // release all queued user input events + for (int i = 0; i < d->queuedUserInputEvents.count(); ++i) { + NSEvent *nsevent = static_cast<NSEvent *>(d->queuedUserInputEvents.at(i)); + [nsevent release]; + } // Remove CFSockets from the runloop. for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) { |