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 plugins 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/****************************************************************************
41**
42** Copyright (c) 2007-2008, Apple, Inc.
43**
44** All rights reserved.
45**
46** Redistribution and use in source and binary forms, with or without
47** modification, are permitted provided that the following conditions are met:
48**
49**   * Redistributions of source code must retain the above copyright notice,
50**     this list of conditions and the following disclaimer.
51**
52**   * Redistributions in binary form must reproduce the above copyright notice,
53**     this list of conditions and the following disclaimer in the documentation
54**     and/or other materials provided with the distribution.
55**
56**   * Neither the name of Apple, Inc. nor the names of its contributors
57**     may be used to endorse or promote products derived from this software
58**     without specific prior written permission.
59**
60** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
61** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
62** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
63** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
64** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
65** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
66** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
67** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
68** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
69** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
70** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
71**
72****************************************************************************/
73
74#include "qcocoaeventdispatcher.h"
75#include "qcocoawindow.h"
76#include "qcocoahelpers.h"
77
78#include <QtGui/qevent.h>
79#include <QtGui/qguiapplication.h>
80#include <QtGui/private/qguiapplication_p.h>
81
82#include <QtCore/qmutex.h>
83#include <QtCore/qscopeguard.h>
84#include <QtCore/qsocketnotifier.h>
85#include <QtCore/private/qthread_p.h>
86
87#include <qpa/qplatformwindow.h>
88#include <qpa/qplatformnativeinterface.h>
89
90#include <QtCore/qdebug.h>
91
92#include <AppKit/AppKit.h>
93
94QT_BEGIN_NAMESPACE
95
96static inline CFRunLoopRef mainRunLoop()
97{
98    return CFRunLoopGetMain();
99}
100
101static Boolean runLoopSourceEqualCallback(const void *info1, const void *info2)
102{
103    return info1 == info2;
104}
105
106/*****************************************************************************
107  Timers stuff
108 *****************************************************************************/
109
110/* timer call back */
111void QCocoaEventDispatcherPrivate::runLoopTimerCallback(CFRunLoopTimerRef, void *info)
112{
113    QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
114    if (d->processEventsCalled && (d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
115        // processEvents() was called "manually," ignore this source for now
116        d->maybeCancelWaitForMoreEvents();
117        return;
118    }
119    CFRunLoopSourceSignal(d->activateTimersSourceRef);
120}
121
122void QCocoaEventDispatcherPrivate::activateTimersSourceCallback(void *info)
123{
124    QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
125    d->processTimers();
126    d->maybeCancelWaitForMoreEvents();
127}
128
129bool QCocoaEventDispatcherPrivate::processTimers()
130{
131    int activated = timerInfoList.activateTimers();
132    maybeStartCFRunLoopTimer();
133    return activated > 0;
134}
135
136void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
137{
138    if (timerInfoList.isEmpty()) {
139        // no active timers, so the CFRunLoopTimerRef should not be active either
140        Q_ASSERT(!runLoopTimerRef);
141        return;
142    }
143
144    if (!runLoopTimerRef) {
145        // start the CFRunLoopTimer
146        CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
147        CFTimeInterval interval;
148        CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.);
149
150        // Q: when should the CFRunLoopTimer fire for the first time?
151        struct timespec tv;
152        if (timerInfoList.timerWait(tv)) {
153            // A: when we have timers to fire, of course
154            interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001);
155        } else {
156            // this shouldn't really happen, but in case it does, set the timer to fire a some point in the distant future
157            interval = oneyear;
158        }
159
160        ttf += interval;
161        CFRunLoopTimerContext info = { 0, this, nullptr, nullptr, nullptr };
162        // create the timer with a large interval, as recommended by the CFRunLoopTimerSetNextFireDate()
163        // documentation, since we will adjust the timer's time-to-fire as needed to keep Qt timers working
164        runLoopTimerRef = CFRunLoopTimerCreate(nullptr, ttf, oneyear, 0, 0, QCocoaEventDispatcherPrivate::runLoopTimerCallback, &info);
165        Q_ASSERT(runLoopTimerRef);
166
167        CFRunLoopAddTimer(mainRunLoop(), runLoopTimerRef, kCFRunLoopCommonModes);
168    } else {
169        // calculate when we need to wake up to process timers again
170        CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
171        CFTimeInterval interval;
172
173        // Q: when should the timer first next?
174        struct timespec tv;
175        if (timerInfoList.timerWait(tv)) {
176            // A: when we have timers to fire, of course
177            interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001);
178        } else {
179            // no timers can fire, but we cannot stop the CFRunLoopTimer, set the timer to fire at some
180            // point in the distant future (the timer interval is one year)
181            interval = CFRunLoopTimerGetInterval(runLoopTimerRef);
182        }
183
184        ttf += interval;
185        CFRunLoopTimerSetNextFireDate(runLoopTimerRef, ttf);
186    }
187}
188
189void QCocoaEventDispatcherPrivate::maybeStopCFRunLoopTimer()
190{
191    if (!runLoopTimerRef)
192        return;
193
194    CFRunLoopTimerInvalidate(runLoopTimerRef);
195    CFRelease(runLoopTimerRef);
196    runLoopTimerRef = nullptr;
197}
198
199void QCocoaEventDispatcher::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *obj)
200{
201#ifndef QT_NO_DEBUG
202    if (timerId < 1 || interval < 0 || !obj) {
203        qWarning("QCocoaEventDispatcher::registerTimer: invalid arguments");
204        return;
205    } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
206        qWarning("QObject::startTimer: timers cannot be started from another thread");
207        return;
208    }
209#endif
210
211    Q_D(QCocoaEventDispatcher);
212    d->timerInfoList.registerTimer(timerId, interval, timerType, obj);
213    d->maybeStartCFRunLoopTimer();
214}
215
216bool QCocoaEventDispatcher::unregisterTimer(int timerId)
217{
218#ifndef QT_NO_DEBUG
219    if (timerId < 1) {
220        qWarning("QCocoaEventDispatcher::unregisterTimer: invalid argument");
221        return false;
222    } else if (thread() != QThread::currentThread()) {
223        qWarning("QObject::killTimer: timers cannot be stopped from another thread");
224        return false;
225    }
226#endif
227
228    Q_D(QCocoaEventDispatcher);
229    bool returnValue = d->timerInfoList.unregisterTimer(timerId);
230    if (!d->timerInfoList.isEmpty())
231        d->maybeStartCFRunLoopTimer();
232    else
233        d->maybeStopCFRunLoopTimer();
234    return returnValue;
235}
236
237bool QCocoaEventDispatcher::unregisterTimers(QObject *obj)
238{
239#ifndef QT_NO_DEBUG
240    if (!obj) {
241        qWarning("QCocoaEventDispatcher::unregisterTimers: invalid argument");
242        return false;
243    } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
244        qWarning("QObject::killTimers: timers cannot be stopped from another thread");
245        return false;
246    }
247#endif
248
249    Q_D(QCocoaEventDispatcher);
250    bool returnValue = d->timerInfoList.unregisterTimers(obj);
251    if (!d->timerInfoList.isEmpty())
252        d->maybeStartCFRunLoopTimer();
253    else
254        d->maybeStopCFRunLoopTimer();
255    return returnValue;
256}
257
258QList<QCocoaEventDispatcher::TimerInfo>
259QCocoaEventDispatcher::registeredTimers(QObject *object) const
260{
261#ifndef QT_NO_DEBUG
262    if (!object) {
263        qWarning("QCocoaEventDispatcher:registeredTimers: invalid argument");
264        return QList<TimerInfo>();
265    }
266#endif
267
268    Q_D(const QCocoaEventDispatcher);
269    return d->timerInfoList.registeredTimers(object);
270}
271
272/*
273    Register a QSocketNotifier with the mac event system by creating a CFSocket with
274    with a read/write callback.
275
276    Qt has separate socket notifiers for reading and writing, but on the mac there is
277    a limitation of one CFSocket object for each native socket.
278*/
279void QCocoaEventDispatcher::registerSocketNotifier(QSocketNotifier *notifier)
280{
281    Q_D(QCocoaEventDispatcher);
282    d->cfSocketNotifier.registerSocketNotifier(notifier);
283}
284
285void QCocoaEventDispatcher::unregisterSocketNotifier(QSocketNotifier *notifier)
286{
287    Q_D(QCocoaEventDispatcher);
288    d->cfSocketNotifier.unregisterSocketNotifier(notifier);
289}
290
291bool QCocoaEventDispatcher::hasPendingEvents()
292{
293    extern uint qGlobalPostedEventsCount();
294    extern bool qt_is_gui_used; //qapplication.cpp
295    return qGlobalPostedEventsCount() || (qt_is_gui_used && !CFRunLoopIsWaiting(CFRunLoopGetMain()));
296}
297
298static bool isUserInputEvent(NSEvent* event)
299{
300    switch ([event type]) {
301    case NSEventTypeLeftMouseDown:
302    case NSEventTypeLeftMouseUp:
303    case NSEventTypeRightMouseDown:
304    case NSEventTypeRightMouseUp:
305    case NSEventTypeMouseMoved:                // ??
306    case NSEventTypeLeftMouseDragged:
307    case NSEventTypeRightMouseDragged:
308    case NSEventTypeMouseEntered:
309    case NSEventTypeMouseExited:
310    case NSEventTypeKeyDown:
311    case NSEventTypeKeyUp:
312    case NSEventTypeFlagsChanged:            // key modifiers changed?
313    case NSEventTypeCursorUpdate:            // ??
314    case NSEventTypeScrollWheel:
315    case NSEventTypeTabletPoint:
316    case NSEventTypeTabletProximity:
317    case NSEventTypeOtherMouseDown:
318    case NSEventTypeOtherMouseUp:
319    case NSEventTypeOtherMouseDragged:
320#ifndef QT_NO_GESTURES
321    case NSEventTypeGesture: // touch events
322    case NSEventTypeMagnify:
323    case NSEventTypeSwipe:
324    case NSEventTypeRotate:
325    case NSEventTypeBeginGesture:
326    case NSEventTypeEndGesture:
327#endif // QT_NO_GESTURES
328        return true;
329        break;
330    default:
331        break;
332    }
333    return false;
334}
335
336static inline void qt_mac_waitForMoreEvents(NSString *runLoopMode = NSDefaultRunLoopMode)
337{
338    // If no event exist in the cocoa event que, wait (and free up cpu time) until
339    // at least one event occur. Setting 'dequeuing' to 'no' in the following call
340    // causes it to hang under certain circumstances (QTBUG-28283), so we tell it
341    // to dequeue instead, just to repost the event again:
342    NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
343        untilDate:[NSDate distantFuture]
344        inMode:runLoopMode
345        dequeue:YES];
346    if (event)
347        [NSApp postEvent:event atStart:YES];
348}
349
350bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
351{
352    Q_D(QCocoaEventDispatcher);
353
354    // In rare rather corner cases a user's application messes with
355    // QEventLoop::exec()/exit() and QCoreApplication::processEvents(),
356    // we have to undo what bool blocker normally does.
357    d->propagateInterrupt = false;
358    const auto boolBlockerUndo = qScopeGuard([d](){
359        if (d->propagateInterrupt)
360            d->interrupt = true;
361        d->propagateInterrupt = false;
362    });
363    QBoolBlocker interruptBlocker(d->interrupt, false);
364
365    bool interruptLater = false;
366    QtCocoaInterruptDispatcher::cancelInterruptLater();
367
368    emit awake();
369
370    uint oldflags = d->processEventsFlags;
371    d->processEventsFlags = flags;
372
373    // Used to determine whether any eventloop has been exec'ed, and allow posted
374    // and timer events to be processed even if this function has never been called
375    // instead of being kept on hold for the next run of processEvents().
376    ++d->processEventsCalled;
377
378    bool excludeUserEvents = d->processEventsFlags & QEventLoop::ExcludeUserInputEvents;
379    bool retVal = false;
380    forever {
381        if (d->interrupt)
382            break;
383
384        QMacAutoReleasePool pool;
385        NSEvent* event = nil;
386
387        // First, send all previously excluded input events, if any:
388        if (d->sendQueuedUserInputEvents())
389            retVal = true;
390
391
392        // If Qt is used as a plugin, or as an extension in a native cocoa
393        // application, we should not run or stop NSApplication; This will be
394        // done from the application itself. And if processEvents is called
395        // manually (rather than from a QEventLoop), we cannot enter a tight
396        // loop and block this call, but instead we need to return after one flush.
397        // Finally, if we are to exclude user input events, we cannot call [NSApp run]
398        // as we then loose control over which events gets dispatched:
399        const bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning];
400        const bool canExec_Qt = (!excludeUserEvents
401                                 && ((d->processEventsFlags & QEventLoop::DialogExec)
402                                     || (d->processEventsFlags & QEventLoop::EventLoopExec)));
403
404        if (canExec_Qt && canExec_3rdParty) {
405            // We can use exec-mode, meaning that we can stay in a tight loop until
406            // interrupted. This is mostly an optimization, but it allow us to use
407            // [NSApp run], which is the normal code path for cocoa applications.
408            if (NSModalSession session = d->currentModalSession()) {
409                QBoolBlocker execGuard(d->currentExecIsNSAppRun, false);
410                while ([NSApp runModalSession:session] == NSModalResponseContinue && !d->interrupt) {
411                    qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode);
412                    if (session != d->currentModalSessionCached) {
413                        // It's possible to release the current modal session
414                        // while we are in this loop, for example, by closing all
415                        // windows from a slot via QApplication::closeAllWindows.
416                        // In this case we cannot use 'session' anymore. A warning
417                        // from Cocoa is: "Use of freed session detected. Do not
418                        // call runModalSession: after calling endModalSesion:."
419                        break;
420                    }
421                }
422
423                if (!d->interrupt && session == d->currentModalSessionCached) {
424                    // Someone called [NSApp stopModal:] from outside the event
425                    // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
426                    // 'session' as well. As a result, we need to restart all internal sessions:
427                    d->temporarilyStopAllModalSessions();
428                }
429
430                // Clean up the modal session list, call endModalSession.
431                if (d->cleanupModalSessionsNeeded)
432                    d->cleanupModalSessions();
433
434            } else {
435                d->nsAppRunCalledByQt = true;
436                QBoolBlocker execGuard(d->currentExecIsNSAppRun, true);
437                [NSApp run];
438            }
439            retVal = true;
440        } else {
441            int lastSerialCopy = d->lastSerial;
442            const bool hadModalSession = d->currentModalSessionCached;
443            // We cannot block the thread (and run in a tight loop).
444            // Instead we will process all current pending events and return.
445            d->ensureNSAppInitialized();
446            if (NSModalSession session = d->currentModalSession()) {
447                // INVARIANT: a modal window is executing.
448                if (!excludeUserEvents) {
449                    // Since we can dispatch all kinds of events, we choose
450                    // to use cocoa's native way of running modal sessions:
451                    if (flags & QEventLoop::WaitForMoreEvents)
452                        qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode);
453                    NSInteger status = [NSApp runModalSession:session];
454                    if (status != NSModalResponseContinue && session == d->currentModalSessionCached) {
455                        // INVARIANT: Someone called [NSApp stopModal:] from outside the event
456                        // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
457                        // 'session' as well. As a result, we need to restart all internal sessions:
458                        d->temporarilyStopAllModalSessions();
459                    }
460
461                    // Clean up the modal session list, call endModalSession.
462                    if (d->cleanupModalSessionsNeeded)
463                        d->cleanupModalSessions();
464
465                    retVal = true;
466                } else do {
467                    // Dispatch all non-user events (but que non-user events up for later). In
468                    // this case, we need more control over which events gets dispatched, and
469                    // cannot use [NSApp runModalSession:session]:
470                    event = [NSApp nextEventMatchingMask:NSEventMaskAny
471                    untilDate:nil
472                    inMode:NSModalPanelRunLoopMode
473                    dequeue: YES];
474
475                    if (event) {
476                        if (isUserInputEvent(event)) {
477                            [event retain];
478                            d->queuedUserInputEvents.append(event);
479                            continue;
480                        }
481                        if (!filterNativeEvent("NSEvent", event, nullptr)) {
482                            [NSApp sendEvent:event];
483                            retVal = true;
484                        }
485                    }
486                } while (!d->interrupt && event);
487            } else do {
488                // INVARIANT: No modal window is executing.
489                event = [NSApp nextEventMatchingMask:NSEventMaskAny
490                untilDate:nil
491                inMode:NSDefaultRunLoopMode
492                dequeue: YES];
493
494                if (event) {
495                    if (flags & QEventLoop::ExcludeUserInputEvents) {
496                        if (isUserInputEvent(event)) {
497                            [event retain];
498                            d->queuedUserInputEvents.append(event);
499                            continue;
500                        }
501                    }
502                    if (!filterNativeEvent("NSEvent", event, nullptr)) {
503                        [NSApp sendEvent:event];
504                        retVal = true;
505                    }
506                }
507            } while (!d->interrupt && event);
508
509            if ((d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
510                // When called "manually", always process posted events and timers
511                bool oldInterrupt = d->interrupt;
512                d->processPostedEvents();
513                if (!oldInterrupt && d->interrupt && !d->currentModalSession()) {
514                    // We had direct processEvent call, coming not from QEventLoop::exec().
515                    // One of the posted events triggered an application to interrupt the loop.
516                    // But bool blocker will reset d->interrupt to false, so the real event
517                    // loop will never notice it was interrupted. Now we'll have to fix it by
518                    // enforcing the value of d->interrupt.
519                    d->propagateInterrupt = true;
520                }
521                retVal = d->processTimers() || retVal;
522            }
523
524            // be sure to return true if the posted event source fired
525            retVal = retVal || lastSerialCopy != d->lastSerial;
526
527            // Since the window that holds modality might have changed while processing
528            // events, we we need to interrupt when we return back the previous process
529            // event recursion to ensure that we spin the correct modal session.
530            // We do the interruptLater at the end of the function to ensure that we don't
531            // disturb the 'wait for more events' below (as deleteLater will post an event):
532            if (hadModalSession && !d->currentModalSessionCached)
533                interruptLater = true;
534        }
535        bool canWait = (d->threadData.loadRelaxed()->canWait
536                && !retVal
537                && !d->interrupt
538                && (d->processEventsFlags & QEventLoop::WaitForMoreEvents));
539        if (canWait) {
540            // INVARIANT: We haven't processed any events yet. And we're told
541            // to stay inside this function until at least one event is processed.
542            qt_mac_waitForMoreEvents();
543            d->processEventsFlags &= ~QEventLoop::WaitForMoreEvents;
544        } else {
545            // Done with event processing for now.
546            // Leave the function:
547            break;
548        }
549    }
550
551    d->processEventsFlags = oldflags;
552    --d->processEventsCalled;
553
554    // If we're interrupted, we need to interrupt the _current_
555    // recursion as well to check if it is  still supposed to be
556    // executing. This way we wind down the stack until we land
557    // on a recursion that again calls processEvents (typically
558    // from QEventLoop), and set interrupt to false:
559    if (d->interrupt)
560        interrupt();
561
562    if (interruptLater)
563        QtCocoaInterruptDispatcher::interruptLater();
564
565    return retVal;
566}
567
568int QCocoaEventDispatcher::remainingTime(int timerId)
569{
570#ifndef QT_NO_DEBUG
571    if (timerId < 1) {
572        qWarning("QCocoaEventDispatcher::remainingTime: invalid argument");
573        return -1;
574    }
575#endif
576
577    Q_D(QCocoaEventDispatcher);
578    return d->timerInfoList.timerRemainingTime(timerId);
579}
580
581void QCocoaEventDispatcher::wakeUp()
582{
583    Q_D(QCocoaEventDispatcher);
584    d->serialNumber.ref();
585    CFRunLoopSourceSignal(d->postedEventsSource);
586    CFRunLoopWakeUp(mainRunLoop());
587}
588
589/*****************************************************************************
590  QEventDispatcherMac Implementation
591 *****************************************************************************/
592
593void QCocoaEventDispatcherPrivate::ensureNSAppInitialized()
594{
595    // Some elements in Cocoa require NSApplication to be running before
596    // they get fully initialized, in particular the menu bar. This
597    // function is intended for cases where a dialog is told to execute before
598    // QGuiApplication::exec is called, or the application spins the events loop
599    // manually rather than calling QGuiApplication:exec.
600    // The function makes sure that NSApplication starts running, but stops
601    // it again as soon as the send posted events callback is called. That way
602    // we let Cocoa finish the initialization it seems to need. We'll only
603    // apply this trick at most once for any application, and we avoid doing it
604    // for the common case where main just starts QGuiApplication::exec.
605    if (nsAppRunCalledByQt || [NSApp isRunning])
606        return;
607    nsAppRunCalledByQt = true;
608    QBoolBlocker block1(interrupt, true);
609    QBoolBlocker block2(currentExecIsNSAppRun, true);
610    [NSApp run];
611}
612
613void QCocoaEventDispatcherPrivate::temporarilyStopAllModalSessions()
614{
615    // Flush, and Stop, all created modal session, and as
616    // such, make them pending again. The next call to
617    // currentModalSession will recreate them again. The
618    // reason to stop all session like this is that otherwise
619    // a call [NSApp stop] would not stop NSApp, but rather
620    // the current modal session. So if we need to stop NSApp
621    // we need to stop all the modal session first. To avoid changing
622    // the stacking order of the windows while doing so, we put
623    // up a block that is used in QCocoaWindow and QCocoaPanel:
624    int stackSize = cocoaModalSessionStack.size();
625    for (int i=0; i<stackSize; ++i) {
626        QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
627        if (info.session) {
628            [NSApp endModalSession:info.session];
629            info.session = nullptr;
630            [(NSWindow*) info.nswindow release];
631        }
632    }
633    currentModalSessionCached = nullptr;
634}
635
636NSModalSession QCocoaEventDispatcherPrivate::currentModalSession()
637{
638    // If we have one or more modal windows, this function will create
639    // a session for each of those, and return the one for the top.
640    if (currentModalSessionCached)
641        return currentModalSessionCached;
642
643    if (cocoaModalSessionStack.isEmpty())
644        return nullptr;
645
646    int sessionCount = cocoaModalSessionStack.size();
647    for (int i=0; i<sessionCount; ++i) {
648        QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
649        if (!info.window)
650            continue;
651
652        if (!info.session) {
653            QMacAutoReleasePool pool;
654            QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(info.window->handle());
655            if (!cocoaWindow)
656                continue;
657            NSWindow *nswindow = cocoaWindow->nativeWindow();
658            if (!nswindow)
659                continue;
660
661            ensureNSAppInitialized();
662            QBoolBlocker block1(blockSendPostedEvents, true);
663            info.nswindow = nswindow;
664            [(NSWindow*) info.nswindow retain];
665            QRect rect = cocoaWindow->geometry();
666            info.session = [NSApp beginModalSessionForWindow:nswindow];
667
668            // The call to beginModalSessionForWindow above processes events and may
669            // have deleted or destroyed the window. Check if it's still valid.
670            if (!info.window)
671                continue;
672            cocoaWindow = static_cast<QCocoaWindow *>(info.window->handle());
673            if (!cocoaWindow)
674                continue;
675
676            if (rect != cocoaWindow->geometry())
677                cocoaWindow->setGeometry(rect);
678        }
679        currentModalSessionCached = info.session;
680        cleanupModalSessionsNeeded = false;
681    }
682    return currentModalSessionCached;
683}
684
685bool QCocoaEventDispatcherPrivate::hasModalSession() const
686{
687    return !cocoaModalSessionStack.isEmpty();
688}
689
690void QCocoaEventDispatcherPrivate::cleanupModalSessions()
691{
692    // Go through the list of modal sessions, and end those
693    // that no longer has a window assosiated; no window means
694    // the session has logically ended. The reason we wait like
695    // this to actually end the sessions for real (rather than at the
696    // point they were marked as stopped), is that ending a session
697    // when no other session runs below it on the stack will make cocoa
698    // drop some events on the floor.
699    QMacAutoReleasePool pool;
700    int stackSize = cocoaModalSessionStack.size();
701
702    for (int i=stackSize-1; i>=0; --i) {
703        QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
704        if (info.window) {
705            // This session has a window, and is therefore not marked
706            // as stopped. So just make it current. There might still be other
707            // stopped sessions on the stack, but those will be stopped on
708            // a later "cleanup" call.
709            currentModalSessionCached = info.session;
710            break;
711        }
712        currentModalSessionCached = nullptr;
713        if (info.session) {
714            Q_ASSERT(info.nswindow);
715            [NSApp endModalSession:info.session];
716            [(NSWindow *)info.nswindow release];
717        }
718        // remove the info now that we are finished with it
719        cocoaModalSessionStack.remove(i);
720    }
721
722    cleanupModalSessionsNeeded = false;
723}
724
725void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window)
726{
727    // We need to start spinning the modal session. Usually this is done with
728    // QDialog::exec() for Qt Widgets based applications, but for others that
729    // just call show(), we need to interrupt().
730    Q_Q(QCocoaEventDispatcher);
731    q->interrupt();
732
733    // Add a new, empty (null), NSModalSession to the stack.
734    // It will become active the next time QEventDispatcher::processEvents is called.
735    // A QCocoaModalSessionInfo is considered pending to become active if the window pointer
736    // is non-zero, and the session pointer is zero (it will become active upon a call to
737    // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if
738    // the window pointer is zero, and the session pointer is non-zero (it will be fully
739    // stopped in cleanupModalSessions()).
740    QCocoaModalSessionInfo info = {window, nullptr, nullptr};
741    cocoaModalSessionStack.push(info);
742    currentModalSessionCached = nullptr;
743}
744
745void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window)
746{
747    Q_Q(QCocoaEventDispatcher);
748
749    // Mark all sessions attached to window as pending to be stopped. We do this
750    // by setting the window pointer to zero, but leave the session pointer.
751    // We don't tell cocoa to stop any sessions just yet, because cocoa only understands
752    // when we stop the _current_ modal session (which is the session on top of
753    // the stack, and might not belong to 'window').
754    int stackSize = cocoaModalSessionStack.size();
755    int endedSessions = 0;
756    for (int i=stackSize-1; i>=0; --i) {
757        QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
758        if (!info.window)
759            endedSessions++;
760        if (info.window == window) {
761            info.window = nullptr;
762            if (i + endedSessions == stackSize-1) {
763                // The top sessions ended. Interrupt the event dispatcher to
764                // start spinning the correct session immediately.
765                q->interrupt();
766                currentModalSessionCached = nullptr;
767                cleanupModalSessionsNeeded = true;
768            }
769        }
770    }
771}
772
773QCocoaEventDispatcherPrivate::QCocoaEventDispatcherPrivate()
774    : processEventsFlags(0),
775      runLoopTimerRef(nullptr),
776      blockSendPostedEvents(false),
777      currentExecIsNSAppRun(false),
778      nsAppRunCalledByQt(false),
779      cleanupModalSessionsNeeded(false),
780      processEventsCalled(0),
781      currentModalSessionCached(nullptr),
782      lastSerial(-1),
783      interrupt(false)
784{
785}
786
787void qt_mac_maybeCancelWaitForMoreEventsForwarder(QAbstractEventDispatcher *eventDispatcher)
788{
789    static_cast<QCocoaEventDispatcher *>(eventDispatcher)->d_func()->maybeCancelWaitForMoreEvents();
790}
791
792QCocoaEventDispatcher::QCocoaEventDispatcher(QObject *parent)
793    : QAbstractEventDispatcher(*new QCocoaEventDispatcherPrivate, parent)
794{
795    Q_D(QCocoaEventDispatcher);
796
797    d->cfSocketNotifier.setHostEventDispatcher(this);
798    d->cfSocketNotifier.setMaybeCancelWaitForMoreEventsCallback(qt_mac_maybeCancelWaitForMoreEventsForwarder);
799
800    // keep our sources running when modal loops are running
801    CFRunLoopAddCommonMode(mainRunLoop(), (CFStringRef) NSModalPanelRunLoopMode);
802
803    CFRunLoopSourceContext context;
804    bzero(&context, sizeof(CFRunLoopSourceContext));
805    context.info = d;
806    context.equal = runLoopSourceEqualCallback;
807
808    // source used to activate timers
809    context.perform = QCocoaEventDispatcherPrivate::activateTimersSourceCallback;
810    d->activateTimersSourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
811    Q_ASSERT(d->activateTimersSourceRef);
812    CFRunLoopAddSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
813
814    // source used to send posted events
815    context.perform = QCocoaEventDispatcherPrivate::postedEventsSourceCallback;
816    d->postedEventsSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
817    Q_ASSERT(d->postedEventsSource);
818    CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
819
820    // observer to emit aboutToBlock() and awake()
821    CFRunLoopObserverContext observerContext;
822    bzero(&observerContext, sizeof(CFRunLoopObserverContext));
823    observerContext.info = this;
824    d->waitingObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
825                                                 kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting,
826                                                 true, 0,
827                                                 QCocoaEventDispatcherPrivate::waitingObserverCallback,
828                                                 &observerContext);
829    CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes);
830
831    /* The first cycle in the loop adds the source and the events of the source
832       are not processed.
833       We use an observer to process the posted events for the first
834       execution of the loop. */
835    CFRunLoopObserverContext firstTimeObserverContext;
836    bzero(&firstTimeObserverContext, sizeof(CFRunLoopObserverContext));
837    firstTimeObserverContext.info = d;
838    d->firstTimeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
839                                                   kCFRunLoopEntry,
840                                                   /* repeats = */ false,
841                                                   0,
842                                                   QCocoaEventDispatcherPrivate::firstLoopEntry,
843                                                   &firstTimeObserverContext);
844    CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, kCFRunLoopCommonModes);
845}
846
847void QCocoaEventDispatcherPrivate::waitingObserverCallback(CFRunLoopObserverRef,
848                                                          CFRunLoopActivity activity, void *info)
849{
850    if (activity == kCFRunLoopBeforeWaiting)
851        emit static_cast<QCocoaEventDispatcher*>(info)->aboutToBlock();
852    else
853        emit static_cast<QCocoaEventDispatcher*>(info)->awake();
854}
855
856bool QCocoaEventDispatcherPrivate::sendQueuedUserInputEvents()
857{
858    Q_Q(QCocoaEventDispatcher);
859    if (processEventsFlags & QEventLoop::ExcludeUserInputEvents)
860        return false;
861    bool didSendEvent = false;
862    while (!queuedUserInputEvents.isEmpty()) {
863        NSEvent *event = static_cast<NSEvent *>(queuedUserInputEvents.takeFirst());
864        if (!q->filterNativeEvent("NSEvent", event, nullptr)) {
865            [NSApp sendEvent:event];
866            didSendEvent = true;
867        }
868        [event release];
869    }
870    return didSendEvent;
871}
872
873void QCocoaEventDispatcherPrivate::processPostedEvents()
874{
875    if (blockSendPostedEvents) {
876        // We're told to not send posted events (because the event dispatcher
877        // is currently working on setting up the correct session to run). But
878        // we still need to make sure that we don't fall asleep until pending events
879        // are sendt, so we just signal this need, and return:
880        CFRunLoopSourceSignal(postedEventsSource);
881        return;
882    }
883
884    if (cleanupModalSessionsNeeded && currentExecIsNSAppRun)
885        cleanupModalSessions();
886
887    if (processEventsCalled > 0 && interrupt) {
888        if (currentExecIsNSAppRun) {
889            // The event dispatcher has been interrupted. But since
890            // [NSApplication run] is running the event loop, we
891            // delayed stopping it until now (to let cocoa process
892            // pending cocoa events first).
893            if (currentModalSessionCached)
894                temporarilyStopAllModalSessions();
895            [NSApp stop:NSApp];
896            cancelWaitForMoreEvents();
897        }
898        return;
899    }
900
901    int serial = serialNumber.loadRelaxed();
902    if (!threadData.loadRelaxed()->canWait || (serial != lastSerial)) {
903        lastSerial = serial;
904        QCoreApplication::sendPostedEvents();
905        QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::AllEvents);
906    }
907}
908
909void QCocoaEventDispatcherPrivate::firstLoopEntry(CFRunLoopObserverRef ref,
910                                                CFRunLoopActivity activity,
911                                                void *info)
912{
913    Q_UNUSED(ref);
914    Q_UNUSED(activity);
915    static_cast<QCocoaEventDispatcherPrivate *>(info)->processPostedEvents();
916}
917
918void QCocoaEventDispatcherPrivate::postedEventsSourceCallback(void *info)
919{
920    QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
921    if (d->processEventsCalled && (d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
922        // processEvents() was called "manually," ignore this source for now
923        d->maybeCancelWaitForMoreEvents();
924        return;
925    }
926    d->sendQueuedUserInputEvents();
927    d->processPostedEvents();
928    d->maybeCancelWaitForMoreEvents();
929}
930
931void QCocoaEventDispatcherPrivate::cancelWaitForMoreEvents()
932{
933    // In case the event dispatcher is waiting for more
934    // events somewhere, we post a dummy event to wake it up:
935    QMacAutoReleasePool pool;
936    [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined location:NSZeroPoint
937        modifierFlags:0 timestamp:0. windowNumber:0 context:nil
938        subtype:QtCocoaEventSubTypeWakeup data1:0 data2:0] atStart:NO];
939}
940
941void QCocoaEventDispatcherPrivate::maybeCancelWaitForMoreEvents()
942{
943    if ((processEventsFlags & (QEventLoop::EventLoopExec | QEventLoop::WaitForMoreEvents)) == QEventLoop::WaitForMoreEvents) {
944        // RunLoop sources are not NSEvents, but they do generate Qt events. If
945        // WaitForMoreEvents was set, but EventLoopExec is not, processEvents()
946        // should return after a source has sent some Qt events.
947        cancelWaitForMoreEvents();
948    }
949}
950
951void QCocoaEventDispatcher::interrupt()
952{
953    Q_D(QCocoaEventDispatcher);
954    d->interrupt = true;
955    wakeUp();
956
957    // We do nothing more here than setting d->interrupt = true, and
958    // poke the event loop if it is sleeping. Actually stopping
959    // NSApp, or the current modal session, is done inside the send
960    // posted events callback. We do this to ensure that all current pending
961    // cocoa events gets delivered before we stop. Otherwise, if we now stop
962    // the last event loop recursion, cocoa will just drop pending posted
963    // events on the floor before we get a chance to reestablish a new session.
964    d->cancelWaitForMoreEvents();
965}
966
967void QCocoaEventDispatcher::flush()
968{ }
969
970// QTBUG-56746: The behavior of processEvents() has been changed to not clear
971// the interrupt flag. Use this function to clear it.
972 void QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag()
973{
974    QCocoaEventDispatcher *cocoaEventDispatcher =
975            qobject_cast<QCocoaEventDispatcher *>(QThread::currentThread()->eventDispatcher());
976    if (!cocoaEventDispatcher)
977        return;
978    QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate =
979            static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher));
980    cocoaEventDispatcherPrivate->interrupt = false;
981}
982
983QCocoaEventDispatcher::~QCocoaEventDispatcher()
984{
985    Q_D(QCocoaEventDispatcher);
986
987    qDeleteAll(d->timerInfoList);
988    d->maybeStopCFRunLoopTimer();
989    CFRunLoopRemoveSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
990    CFRelease(d->activateTimersSourceRef);
991
992    // end all modal sessions
993    for (int i = 0; i < d->cocoaModalSessionStack.count(); ++i) {
994        QCocoaModalSessionInfo &info = d->cocoaModalSessionStack[i];
995        if (info.session) {
996            [NSApp endModalSession:info.session];
997            [(NSWindow *)info.nswindow release];
998        }
999    }
1000
1001    // release all queued user input events
1002    for (int i = 0; i < d->queuedUserInputEvents.count(); ++i) {
1003        NSEvent *nsevent = static_cast<NSEvent *>(d->queuedUserInputEvents.at(i));
1004        [nsevent release];
1005    }
1006
1007    d->cfSocketNotifier.removeSocketNotifiers();
1008
1009    CFRunLoopRemoveSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
1010    CFRelease(d->postedEventsSource);
1011
1012    CFRunLoopObserverInvalidate(d->waitingObserver);
1013    CFRelease(d->waitingObserver);
1014
1015    CFRunLoopObserverInvalidate(d->firstTimeObserver);
1016    CFRelease(d->firstTimeObserver);
1017}
1018
1019QtCocoaInterruptDispatcher* QtCocoaInterruptDispatcher::instance = nullptr;
1020
1021QtCocoaInterruptDispatcher::QtCocoaInterruptDispatcher() : cancelled(false)
1022{
1023    // The whole point of this class is that we enable a way to interrupt
1024    // the event dispatcher when returning back to a lower recursion level
1025    // than where interruptLater was called. This is needed to detect if
1026    // [NSApp run] should still be running at the recursion level it is at.
1027    // Since the interrupt is canceled if processEvents is called before
1028    // this object gets deleted, we also avoid interrupting unnecessary.
1029    deleteLater();
1030}
1031
1032QtCocoaInterruptDispatcher::~QtCocoaInterruptDispatcher()
1033{
1034    if (cancelled)
1035        return;
1036    instance = nullptr;
1037    QCocoaEventDispatcher::instance()->interrupt();
1038}
1039
1040void QtCocoaInterruptDispatcher::cancelInterruptLater()
1041{
1042    if (!instance)
1043        return;
1044    instance->cancelled = true;
1045    delete instance;
1046    instance = nullptr;
1047}
1048
1049void QtCocoaInterruptDispatcher::interruptLater()
1050{
1051    cancelInterruptLater();
1052    instance = new QtCocoaInterruptDispatcher;
1053}
1054
1055QT_END_NAMESPACE
1056
1057