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