summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm')
-rw-r--r--src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm154
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) {