Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qcocoaeventdispatcher.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4/****************************************************************************
5**
6** Copyright (c) 2007-2008, Apple, Inc.
7**
8** All rights reserved.
9**
10** Redistribution and use in source and binary forms, with or without
11** modification, are permitted provided that the following conditions are met:
12**
13** * Redistributions of source code must retain the above copyright notice,
14** this list of conditions and the following disclaimer.
15**
16** * Redistributions in binary form must reproduce the above copyright notice,
17** this list of conditions and the following disclaimer in the documentation
18** and/or other materials provided with the distribution.
19**
20** * Neither the name of Apple, Inc. nor the names of its contributors
21** may be used to endorse or promote products derived from this software
22** without specific prior written permission.
23**
24** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35**
36****************************************************************************/
37
38#include <AppKit/AppKit.h>
39
41#include "qcocoawindow.h"
42#include "qcocoahelpers.h"
43
44#include <QtGui/qevent.h>
45#include <QtGui/qguiapplication.h>
46#include <QtGui/private/qguiapplication_p.h>
47
48#include <QtCore/qmutex.h>
49#include <QtCore/qscopeguard.h>
50#include <QtCore/qsocketnotifier.h>
51#include <QtCore/private/qthread_p.h>
52#include <QtCore/private/qcore_mac_p.h>
53
54#include <qpa/qplatformwindow.h>
55#include <qpa/qplatformnativeinterface.h>
56
57#include <QtCore/qdebug.h>
58
60
61Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher");
62
63static inline CFRunLoopRef mainRunLoop()
64{
65 return CFRunLoopGetMain();
66}
67
68static Boolean runLoopSourceEqualCallback(const void *info1, const void *info2)
69{
70 return info1 == info2;
71}
72
73/*****************************************************************************
74 Timers stuff
75 *****************************************************************************/
76
77/* timer call back */
79{
81 if (d->processEventsCalled && (d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
82 // processEvents() was called "manually," ignore this source for now
84 return;
85 }
86 CFRunLoopSourceSignal(d->activateTimersSourceRef);
87}
88
90{
92 if (d->initializingNSApplication) {
93 qCDebug(lcEventDispatcher) << "Deferring" << __FUNCTION__ << "due to NSApp initialization";
94 // We don't want to process any sources during explicit NSApplication
95 // initialization, so defer the source until the actual event processing.
96 CFRunLoopSourceSignal(d->activateTimersSourceRef);
97 return;
98 }
99 d->processTimers();
100 d->maybeCancelWaitForMoreEvents();
101}
102
104{
105 int activated = timerInfoList.activateTimers();
107 return activated > 0;
108}
109
111{
112 if (timerInfoList.isEmpty()) {
113 // no active timers, so the CFRunLoopTimerRef should not be active either
115 return;
116 }
117
118 using DoubleSeconds = std::chrono::duration<double, std::ratio<1>>;
119 if (!runLoopTimerRef) {
120 // start the CFRunLoopTimer
121 CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
122 CFTimeInterval interval;
123 CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.);
124
125 // Q: when should the CFRunLoopTimer fire for the first time?
126 if (auto opt = timerInfoList.timerWait()) {
127 // A: when we have timers to fire, of course
128 DoubleSeconds secs{*opt};
129 interval = qMax(secs.count(), 0.0000001);
130 } else {
131 // this shouldn't really happen, but in case it does, set the timer to fire a some point in the distant future
132 interval = oneyear;
133 }
134
135 ttf += interval;
136 CFRunLoopTimerContext info = { 0, this, nullptr, nullptr, nullptr };
137 // create the timer with a large interval, as recommended by the CFRunLoopTimerSetNextFireDate()
138 // documentation, since we will adjust the timer's time-to-fire as needed to keep Qt timers working
139 runLoopTimerRef = CFRunLoopTimerCreate(nullptr, ttf, oneyear, 0, 0, QCocoaEventDispatcherPrivate::runLoopTimerCallback, &info);
141
142 CFRunLoopAddTimer(mainRunLoop(), runLoopTimerRef, kCFRunLoopCommonModes);
143 } else {
144 // calculate when we need to wake up to process timers again
145 CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
146 CFTimeInterval interval;
147
148 // Q: when should the timer first next?
149 if (auto opt = timerInfoList.timerWait()) {
150 // A: when we have timers to fire, of course
151 DoubleSeconds secs{*opt};
152 interval = qMax(secs.count(), 0.0000001);
153 } else {
154 // no timers can fire, but we cannot stop the CFRunLoopTimer, set the timer to fire at some
155 // point in the distant future (the timer interval is one year)
156 interval = CFRunLoopTimerGetInterval(runLoopTimerRef);
157 }
158
159 ttf += interval;
160 CFRunLoopTimerSetNextFireDate(runLoopTimerRef, ttf);
161 }
162}
163
165{
166 if (!runLoopTimerRef)
167 return;
168
169 CFRunLoopTimerInvalidate(runLoopTimerRef);
170 CFRelease(runLoopTimerRef);
171 runLoopTimerRef = nullptr;
172}
173
175 Qt::TimerType timerType, QObject *obj)
176{
177#ifndef QT_NO_DEBUG
178 if (qToUnderlying(timerId) < 1 || interval.count() < 0 || !obj) {
179 qWarning("QCocoaEventDispatcher::registerTimer: invalid arguments");
180 return;
181 } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
182 qWarning("QObject::startTimer: timers cannot be started from another thread");
183 return;
184 }
185#endif
186
188 d->timerInfoList.registerTimer(timerId, interval, timerType, obj);
189 d->maybeStartCFRunLoopTimer();
190}
191
193{
194#ifndef QT_NO_DEBUG
195 if (qToUnderlying(timerId) < 1) {
196 qWarning("QCocoaEventDispatcher::unregisterTimer: invalid argument");
197 return false;
198 } else if (thread() != QThread::currentThread()) {
199 qWarning("QObject::killTimer: timers cannot be stopped from another thread");
200 return false;
201 }
202#endif
203
205 bool returnValue = d->timerInfoList.unregisterTimer(timerId);
206 if (!d->timerInfoList.isEmpty())
207 d->maybeStartCFRunLoopTimer();
208 else
209 d->maybeStopCFRunLoopTimer();
210 return returnValue;
211}
212
214{
215#ifndef QT_NO_DEBUG
216 if (!obj) {
217 qWarning("QCocoaEventDispatcher::unregisterTimers: invalid argument");
218 return false;
219 } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
220 qWarning("QObject::killTimers: timers cannot be stopped from another thread");
221 return false;
222 }
223#endif
224
226 bool returnValue = d->timerInfoList.unregisterTimers(obj);
227 if (!d->timerInfoList.isEmpty())
228 d->maybeStartCFRunLoopTimer();
229 else
230 d->maybeStopCFRunLoopTimer();
231 return returnValue;
232}
233
234QList<QCocoaEventDispatcher::TimerInfoV2>
236{
237#ifndef QT_NO_DEBUG
238 if (!object) {
239 qWarning("QCocoaEventDispatcher:registeredTimers: invalid argument");
240 return {};
241 }
242#endif
243
244 Q_D(const QCocoaEventDispatcher);
245 return d->timerInfoList.registeredTimers(object);
246}
247
248/*
249 Register a QSocketNotifier with the mac event system by creating a CFSocket with
250 with a read/write callback.
251
252 Qt has separate socket notifiers for reading and writing, but on the mac there is
253 a limitation of one CFSocket object for each native socket.
254*/
256{
258 d->cfSocketNotifier.registerSocketNotifier(notifier);
259}
260
262{
264 d->cfSocketNotifier.unregisterSocketNotifier(notifier);
265}
266
267static bool isUserInputEvent(NSEvent* event)
268{
269 switch ([event type]) {
270 case NSEventTypeLeftMouseDown:
271 case NSEventTypeLeftMouseUp:
272 case NSEventTypeRightMouseDown:
273 case NSEventTypeRightMouseUp:
274 case NSEventTypeMouseMoved: // ??
275 case NSEventTypeLeftMouseDragged:
276 case NSEventTypeRightMouseDragged:
277 case NSEventTypeMouseEntered:
278 case NSEventTypeMouseExited:
279 case NSEventTypeKeyDown:
280 case NSEventTypeKeyUp:
281 case NSEventTypeFlagsChanged: // key modifiers changed?
282 case NSEventTypeCursorUpdate: // ??
283 case NSEventTypeScrollWheel:
284 case NSEventTypeTabletPoint:
285 case NSEventTypeTabletProximity:
286 case NSEventTypeOtherMouseDown:
287 case NSEventTypeOtherMouseUp:
288 case NSEventTypeOtherMouseDragged:
289#ifndef QT_NO_GESTURES
290 case NSEventTypeGesture: // touch events
291 case NSEventTypeMagnify:
292 case NSEventTypeSwipe:
293 case NSEventTypeRotate:
294 case NSEventTypeBeginGesture:
295 case NSEventTypeEndGesture:
296#endif // QT_NO_GESTURES
297 return true;
298 break;
299 default:
300 break;
301 }
302 return false;
303}
304
305static inline void qt_mac_waitForMoreEvents(NSString *runLoopMode = NSDefaultRunLoopMode)
306{
307 // If no event exist in the cocoa event que, wait (and free up cpu time) until
308 // at least one event occur. Setting 'dequeuing' to 'no' in the following call
309 // causes it to hang under certain circumstances (QTBUG-28283), so we tell it
310 // to dequeue instead, just to repost the event again:
311 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
312 untilDate:[NSDate distantFuture]
313 inMode:runLoopMode
314 dequeue:YES];
315 if (event)
316 [NSApp postEvent:event atStart:YES];
317}
318
319bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
320{
322
323 // In rare rather corner cases a user's application messes with
324 // QEventLoop::exec()/exit() and QCoreApplication::processEvents(),
325 // we have to undo what bool blocker normally does.
326 d->propagateInterrupt = false;
327 const auto boolBlockerUndo = qScopeGuard([d](){
328 if (d->propagateInterrupt)
329 d->interrupt = true;
330 d->propagateInterrupt = false;
331 });
332 QBoolBlocker interruptBlocker(d->interrupt, false);
333
334 bool interruptLater = false;
336
337 emit awake();
338
339 uint oldflags = d->processEventsFlags;
340 d->processEventsFlags = flags;
341
342 // Used to determine whether any eventloop has been exec'ed, and allow posted
343 // and timer events to be processed even if this function has never been called
344 // instead of being kept on hold for the next run of processEvents().
345 ++d->processEventsCalled;
346
347 bool excludeUserEvents = d->processEventsFlags & QEventLoop::ExcludeUserInputEvents;
348 bool retVal = false;
349 forever {
350 if (d->interrupt)
351 break;
352
354 NSEvent* event = nil;
355
356 // First, send all previously excluded input events, if any:
357 if (d->sendQueuedUserInputEvents())
358 retVal = true;
359
360
361 // If Qt is used as a plugin, or as an extension in a native cocoa
362 // application, we should not run or stop NSApplication; This will be
363 // done from the application itself. And if processEvents is called
364 // manually (rather than from a QEventLoop), we cannot enter a tight
365 // loop and block this call, but instead we need to return after one flush.
366 // Finally, if we are to exclude user input events, we cannot call [NSApp run]
367 // as we then loose control over which events gets dispatched:
368 const bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning];
369 const bool canExec_Qt = (!excludeUserEvents
370 && ((d->processEventsFlags & QEventLoop::DialogExec)
371 || (d->processEventsFlags & QEventLoop::EventLoopExec)));
372
373 if (canExec_Qt && canExec_3rdParty) {
374 // We can use exec-mode, meaning that we can stay in a tight loop until
375 // interrupted. This is mostly an optimization, but it allow us to use
376 // [NSApp run], which is the normal code path for cocoa applications.
377 if (NSModalSession session = d->currentModalSession()) {
378 QBoolBlocker execGuard(d->currentExecIsNSAppRun, false);
379 qCDebug(lcEventDispatcher) << "Running modal session" << session;
380 while ([NSApp runModalSession:session] == NSModalResponseContinue && !d->interrupt) {
381 qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode);
382 if (session != d->currentModalSessionCached) {
383 // It's possible to release the current modal session
384 // while we are in this loop, for example, by closing all
385 // windows from a slot via QApplication::closeAllWindows.
386 // In this case we cannot use 'session' anymore. A warning
387 // from Cocoa is: "Use of freed session detected. Do not
388 // call runModalSession: after calling endModalSesion:."
389 break;
390 }
391 }
392
393 if (!d->interrupt && session == d->currentModalSessionCached) {
394 // 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 } else {
405 d->nsAppRunCalledByQt = true;
406 QBoolBlocker execGuard(d->currentExecIsNSAppRun, true);
407 [NSApp run];
408 }
409 retVal = true;
410 } else {
411 int lastSerialCopy = d->lastSerial;
412 const bool hadModalSession = d->currentModalSessionCached;
413 // We cannot block the thread (and run in a tight loop).
414 // Instead we will process all current pending events and return.
415 d->ensureNSAppInitialized();
416 if (NSModalSession session = d->currentModalSession()) {
417 // INVARIANT: a modal window is executing.
418 if (!excludeUserEvents) {
419 // Since we can dispatch all kinds of events, we choose
420 // to use cocoa's native way of running modal sessions:
422 qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode);
423 qCDebug(lcEventDispatcher) << "Running modal session" << session;
424 NSInteger status = [NSApp runModalSession:session];
425 if (status != NSModalResponseContinue && session == d->currentModalSessionCached) {
426 // INVARIANT: Someone called [NSApp stopModal:] from outside the event
427 // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
428 // 'session' as well. As a result, we need to restart all internal sessions:
429 d->temporarilyStopAllModalSessions();
430 }
431
432 // Clean up the modal session list, call endModalSession.
433 if (d->cleanupModalSessionsNeeded)
434 d->cleanupModalSessions();
435
436 retVal = true;
437 } else do {
438 // Dispatch all non-user events (but que non-user events up for later). In
439 // this case, we need more control over which events gets dispatched, and
440 // cannot use [NSApp runModalSession:session]:
441 event = [NSApp nextEventMatchingMask:NSEventMaskAny
442 untilDate:nil
443 inMode:NSModalPanelRunLoopMode
444 dequeue: YES];
445
446 if (event) {
447 if (isUserInputEvent(event)) {
448 [event retain];
449 d->queuedUserInputEvents.append(event);
450 continue;
451 }
452 if (!filterNativeEvent("NSEvent", event, nullptr)) {
453 [NSApp sendEvent:event];
454 retVal = true;
455 }
456 }
457 } while (!d->interrupt && event);
458 } else do {
459 // INVARIANT: No modal window is executing.
460 event = [NSApp nextEventMatchingMask:NSEventMaskAny
461 untilDate:nil
462 inMode:NSDefaultRunLoopMode
463 dequeue: YES];
464
465 if (event) {
467 if (isUserInputEvent(event)) {
468 [event retain];
469 d->queuedUserInputEvents.append(event);
470 continue;
471 }
472 }
473 if (!filterNativeEvent("NSEvent", event, nullptr)) {
474 [NSApp sendEvent:event];
475 retVal = true;
476 }
477 }
478
479 // Clean up the modal session list, call endModalSession.
480 if (d->cleanupModalSessionsNeeded)
481 d->cleanupModalSessions();
482
483 } while (!d->interrupt && event);
484
485 if ((d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
486 // When called "manually", always process posted events and timers
487 bool oldInterrupt = d->interrupt;
488 d->processPostedEvents();
489 if (!oldInterrupt && d->interrupt && !d->currentModalSession()) {
490 // We had direct processEvent call, coming not from QEventLoop::exec().
491 // One of the posted events triggered an application to interrupt the loop.
492 // But bool blocker will reset d->interrupt to false, so the real event
493 // loop will never notice it was interrupted. Now we'll have to fix it by
494 // enforcing the value of d->interrupt.
495 d->propagateInterrupt = true;
496 }
497 retVal = d->processTimers() || retVal;
498 }
499
500 // be sure to return true if the posted event source fired
501 retVal = retVal || lastSerialCopy != d->lastSerial;
502
503 // Since the window that holds modality might have changed while processing
504 // events, we we need to interrupt when we return back the previous process
505 // event recursion to ensure that we spin the correct modal session.
506 // We do the interruptLater at the end of the function to ensure that we don't
507 // disturb the 'wait for more events' below (as deleteLater will post an event):
508 if (hadModalSession && !d->currentModalSessionCached)
509 interruptLater = true;
510 }
511 bool canWait = (d->threadData.loadRelaxed()->canWait
512 && !retVal
513 && !d->interrupt
514 && (d->processEventsFlags & QEventLoop::WaitForMoreEvents));
515 if (canWait) {
516 // INVARIANT: We haven't processed any events yet. And we're told
517 // to stay inside this function until at least one event is processed.
519 d->processEventsFlags &= ~QEventLoop::WaitForMoreEvents;
520 } else {
521 // Done with event processing for now.
522 // Leave the function:
523 break;
524 }
525 }
526
527 d->processEventsFlags = oldflags;
528 --d->processEventsCalled;
529
530 // If we're interrupted, we need to interrupt the _current_
531 // recursion as well to check if it is still supposed to be
532 // executing. This way we wind down the stack until we land
533 // on a recursion that again calls processEvents (typically
534 // from QEventLoop), and set interrupt to false:
535 if (d->interrupt)
536 interrupt();
537
538 if (interruptLater)
540
541 return retVal;
542}
543
545{
546#ifndef QT_NO_DEBUG
547 if (qToUnderlying(timerId) < 1) {
548 qWarning("QCocoaEventDispatcher::remainingTime: invalid argument");
549 return Duration::min();
550 }
551#endif
552
553 Q_D(const QCocoaEventDispatcher);
554 return d->timerInfoList.remainingDuration(timerId);
555}
556
558{
560 d->serialNumber.ref();
561 CFRunLoopSourceSignal(d->postedEventsSource);
562 CFRunLoopWakeUp(mainRunLoop());
563}
564
565/*****************************************************************************
566 QEventDispatcherMac Implementation
567 *****************************************************************************/
568
570{
571 // Some elements in Cocoa require NSApplication to be initialized before
572 // use, for example the menu bar. Under normal circumstances this happens
573 // as part of [NSApp run], as a result of a call to QGuiApplication:exec(),
574 // but in the cases where a dialog is asked to execute before that happens,
575 // or the application spins the event loop manually via processEvents(),
576 // we need to explicitly ensure NSApplication initialization.
577
578 // We can unfortunately not do this via NSApplicationLoad(), as the function
579 // bails out early if there's already an NSApplication instance, which is
580 // the case if any code has called [NSApplication sharedApplication],
581 // or its short form 'NSApp'.
582
583 // Instead we do an actual [NSApp run], but stop the application as soon
584 // as possible, ensuring that AppKit will do the required initialization,
585 // including calling [NSApplication finishLaunching].
586
587 // We only apply this trick at most once for any application, and we avoid
588 // doing it for the common case where main just starts QGuiApplication::exec.
589 if (nsAppRunCalledByQt || [NSApp isRunning])
590 return;
591
592 qCDebug(lcEventDispatcher) << "Ensuring NSApplication is initialized";
593 nsAppRunCalledByQt = true;
594
595 // Stopping the application will still process runloop sources before
596 // actually stopping, so we need to explicitly guard our sources from
597 // doing anything, deferring their actions until later.
598 QBoolBlocker initializationGuard(initializingNSApplication, true);
599
600 CFRunLoopPerformBlock(mainRunLoop(), kCFRunLoopCommonModes, ^{
601 qCDebug(lcEventDispatcher) << "NSApplication has been initialized; Stopping NSApp";
602 [NSApp stop:NSApp];
603 cancelWaitForMoreEvents(); // Post event that wakes up the runloop
604 });
605 [NSApp run];
606 qCDebug(lcEventDispatcher) << "Finished ensuring NSApplication is initialized";
607}
608
610{
611 // Flush, and Stop, all created modal session, and as
612 // such, make them pending again. The next call to
613 // currentModalSession will recreate them again. The
614 // reason to stop all session like this is that otherwise
615 // a call [NSApp stop] would not stop NSApp, but rather
616 // the current modal session. So if we need to stop NSApp
617 // we need to stop all the modal session first. To avoid changing
618 // the stacking order of the windows while doing so, we put
619 // up a block that is used in QCocoaWindow and QCocoaPanel:
620 int stackSize = cocoaModalSessionStack.size();
621 for (int i=0; i<stackSize; ++i) {
623 if (info.session) {
624 qCDebug(lcEventDispatcher) << "Temporarily ending modal session" << info.session
625 << "for" << info.nswindow;
626 [NSApp endModalSession:info.session];
627 info.session = nullptr;
628 [(NSWindow*) info.nswindow release];
629 }
630 }
632}
633
635{
636 // If we have one or more modal windows, this function will create
637 // a session for each of those, and return the one for the top.
640
641 if (cocoaModalSessionStack.isEmpty())
642 return nullptr;
643
644 int sessionCount = cocoaModalSessionStack.size();
645 for (int i=0; i<sessionCount; ++i) {
647 if (!info.window)
648 continue;
649
650 if (!info.session) {
652 QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(info.window->handle());
653 if (!cocoaWindow)
654 continue;
655 NSWindow *nswindow = cocoaWindow->nativeWindow();
656 if (!nswindow)
657 continue;
658
661 info.nswindow = nswindow;
662 [(NSWindow*) info.nswindow retain];
663 QRect rect = cocoaWindow->geometry();
664 info.session = [NSApp beginModalSessionForWindow:nswindow];
665 qCDebug(lcEventDispatcher) << "Begun modal session" << info.session
666 << "for" << 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 }
681 }
683}
684
686{
687 return !cocoaModalSessionStack.isEmpty();
688}
689
691{
692 // Go through the list of modal sessions, and end those
693 // that no longer has a window associated; 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.
700 int stackSize = cocoaModalSessionStack.size();
701
702 for (int i=stackSize-1; i>=0; --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.
710 break;
711 }
713 if (info.session) {
714 Q_ASSERT(info.nswindow);
715 qCDebug(lcEventDispatcher) << "Ending modal session" << info.session
716 << "for" << info.nswindow;
717 [NSApp endModalSession:info.session];
718 [(NSWindow *)info.nswindow release];
719 }
720 // remove the info now that we are finished with it
722 }
723
725}
726
728{
729 qCDebug(lcEventDispatcher) << "Adding modal session for" << window;
730
731 if (std::any_of(cocoaModalSessionStack.constBegin(), cocoaModalSessionStack.constEnd(),
732 [&](const auto &sessionInfo) { return sessionInfo.window == window; })) {
733 qCWarning(lcEventDispatcher) << "Modal session for" << window << "already exists!";
734 return;
735 }
736
737 // We need to start spinning the modal session. Usually this is done with
738 // QDialog::exec() for Qt Widgets based applications, but for others that
739 // just call show(), we need to interrupt().
741 q->interrupt();
742
743 // Add a new, empty (null), NSModalSession to the stack.
744 // It will become active the next time QEventDispatcher::processEvents is called.
745 // A QCocoaModalSessionInfo is considered pending to become active if the window pointer
746 // is non-zero, and the session pointer is zero (it will become active upon a call to
747 // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if
748 // the window pointer is zero, and the session pointer is non-zero (it will be fully
749 // stopped in cleanupModalSessions()).
750 QCocoaModalSessionInfo info = {window, nullptr, nullptr};
753}
754
756{
757 qCDebug(lcEventDispatcher) << "Removing modal session for" << window;
758
760
761 // Mark all sessions attached to window as pending to be stopped. We do this
762 // by setting the window pointer to zero, but leave the session pointer.
763 // We don't tell cocoa to stop any sessions just yet, because cocoa only understands
764 // when we stop the _current_ modal session (which is the session on top of
765 // the stack, and might not belong to 'window').
766 int stackSize = cocoaModalSessionStack.size();
767 int endedSessions = 0;
768 for (int i=stackSize-1; i>=0; --i) {
770 if (!info.window)
771 endedSessions++;
772 if (info.window == window) {
773 info.window = nullptr;
774 if (i + endedSessions == stackSize-1) {
775 // The top sessions ended. Interrupt the event dispatcher to
776 // start spinning the correct session immediately.
777 q->interrupt();
780 }
781 }
782 }
783}
784
786 : processEventsFlags(0),
787 runLoopTimerRef(nullptr),
788 blockSendPostedEvents(false),
789 currentExecIsNSAppRun(false),
790 nsAppRunCalledByQt(false),
791 cleanupModalSessionsNeeded(false),
792 processEventsCalled(0),
793 currentModalSessionCached(nullptr),
794 lastSerial(-1),
795 interrupt(false)
796{
797}
798
800{
801 static_cast<QCocoaEventDispatcher *>(eventDispatcher)->d_func()->maybeCancelWaitForMoreEvents();
802}
803
806{
808
809 d->cfSocketNotifier.setHostEventDispatcher(this);
810 d->cfSocketNotifier.setMaybeCancelWaitForMoreEventsCallback(qt_mac_maybeCancelWaitForMoreEventsForwarder);
811
812 // keep our sources running when modal loops are running
813 CFRunLoopAddCommonMode(mainRunLoop(), (CFStringRef) NSModalPanelRunLoopMode);
814
815 CFRunLoopSourceContext context;
816 bzero(&context, sizeof(CFRunLoopSourceContext));
817 context.info = d;
819
820 // source used to activate timers
822 d->activateTimersSourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
823 Q_ASSERT(d->activateTimersSourceRef);
824 CFRunLoopAddSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
825
826 // source used to send posted events
828 d->postedEventsSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
829 Q_ASSERT(d->postedEventsSource);
830 CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
831
832 // observer to emit aboutToBlock() and awake()
833 CFRunLoopObserverContext observerContext;
834 bzero(&observerContext, sizeof(CFRunLoopObserverContext));
835 observerContext.info = this;
836 d->waitingObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
837 kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting,
838 true, 0,
840 &observerContext);
841 CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes);
842}
843
845 CFRunLoopActivity activity, void *info)
846{
847 if (activity == kCFRunLoopBeforeWaiting)
848 emit static_cast<QCocoaEventDispatcher*>(info)->aboutToBlock();
849 else
850 emit static_cast<QCocoaEventDispatcher*>(info)->awake();
851}
852
854{
857 return false;
858 bool didSendEvent = false;
859 while (!queuedUserInputEvents.isEmpty()) {
860 NSEvent *event = static_cast<NSEvent *>(queuedUserInputEvents.takeFirst());
861 if (!q->filterNativeEvent("NSEvent", event, nullptr)) {
862 [NSApp sendEvent:event];
863 didSendEvent = true;
864 }
865 [event release];
866 }
867 return didSendEvent;
868}
869
871{
873 // We're told to not send posted events (because the event dispatcher
874 // is currently working on setting up the correct session to run). But
875 // we still need to make sure that we don't fall asleep until pending events
876 // are sendt, so we just signal this need, and return:
877 CFRunLoopSourceSignal(postedEventsSource);
878 return;
879 }
880
883
884 if (processEventsCalled > 0 && interrupt) {
886 // The event dispatcher has been interrupted. But since
887 // [NSApplication run] is running the event loop, we
888 // delayed stopping it until now (to let cocoa process
889 // pending cocoa events first).
892 [NSApp stop:NSApp];
894 }
895 return;
896 }
897
898 int serial = serialNumber.loadRelaxed();
899 if (!threadData.loadRelaxed()->canWait || (serial != lastSerial)) {
900 lastSerial = serial;
903 }
904}
905
907{
909 if (d->initializingNSApplication) {
910 qCDebug(lcEventDispatcher) << "Deferring" << __FUNCTION__ << "due to NSApp initialization";
911 // We don't want to process any sources during explicit NSApplication
912 // initialization, so defer the source until the actual event processing.
913 CFRunLoopSourceSignal(d->postedEventsSource);
914 return;
915 }
916
917 if (d->processEventsCalled && (d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
918 // processEvents() was called "manually," ignore this source for now
919 d->maybeCancelWaitForMoreEvents();
920 return;
921 }
922 d->sendQueuedUserInputEvents();
923 d->processPostedEvents();
924 d->maybeCancelWaitForMoreEvents();
925}
926
928{
929 // In case the event dispatcher is waiting for more
930 // events somewhere, we post a dummy event to wake it up:
932 [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined location:NSZeroPoint
933 modifierFlags:0 timestamp:0. windowNumber:0 context:nil
934 subtype:QtCocoaEventSubTypeWakeup data1:0 data2:0] atStart:NO];
935}
936
938{
940 // RunLoop sources are not NSEvents, but they do generate Qt events. If
941 // WaitForMoreEvents was set, but EventLoopExec is not, processEvents()
942 // should return after a source has sent some Qt events.
944 }
945}
946
948{
950 d->interrupt = true;
951 wakeUp();
952
953 // We do nothing more here than setting d->interrupt = true, and
954 // poke the event loop if it is sleeping. Actually stopping
955 // NSApp, or the current modal session, is done inside the send
956 // posted events callback. We do this to ensure that all current pending
957 // cocoa events gets delivered before we stop. Otherwise, if we now stop
958 // the last event loop recursion, cocoa will just drop pending posted
959 // events on the floor before we get a chance to reestablish a new session.
960 d->cancelWaitForMoreEvents();
961}
962
963// QTBUG-56746: The behavior of processEvents() has been changed to not clear
964// the interrupt flag. Use this function to clear it.
966{
967 QCocoaEventDispatcher *cocoaEventDispatcher =
968 qobject_cast<QCocoaEventDispatcher *>(QThread::currentThread()->eventDispatcher());
969 if (!cocoaEventDispatcher)
970 return;
971 QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate =
972 static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher));
973 cocoaEventDispatcherPrivate->interrupt = false;
974}
975
977{
979
980 d->timerInfoList.clearTimers();
981 d->maybeStopCFRunLoopTimer();
982 CFRunLoopRemoveSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
983 CFRelease(d->activateTimersSourceRef);
984
985 // end all modal sessions
986 for (int i = 0; i < d->cocoaModalSessionStack.count(); ++i) {
987 QCocoaModalSessionInfo &info = d->cocoaModalSessionStack[i];
988 if (info.session) {
989 qCDebug(lcEventDispatcher) << "Ending modal session" << info.session
990 << "for" << info.nswindow << "during shutdown";
991 [NSApp endModalSession:info.session];
992 [(NSWindow *)info.nswindow release];
993 }
994 }
995
996 // release all queued user input events
997 for (int i = 0; i < d->queuedUserInputEvents.count(); ++i) {
998 NSEvent *nsevent = static_cast<NSEvent *>(d->queuedUserInputEvents.at(i));
999 [nsevent release];
1000 }
1001
1002 d->cfSocketNotifier.removeSocketNotifiers();
1003
1004 CFRunLoopRemoveSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
1005 CFRelease(d->postedEventsSource);
1006
1007 CFRunLoopObserverInvalidate(d->waitingObserver);
1008 CFRelease(d->waitingObserver);
1009}
1010
1011QtCocoaInterruptDispatcher* QtCocoaInterruptDispatcher::instance = nullptr;
1012
1013QtCocoaInterruptDispatcher::QtCocoaInterruptDispatcher() : cancelled(false)
1014{
1015 // The whole point of this class is that we enable a way to interrupt
1016 // the event dispatcher when returning back to a lower recursion level
1017 // than where interruptLater was called. This is needed to detect if
1018 // [NSApp run] should still be running at the recursion level it is at.
1019 // Since the interrupt is canceled if processEvents is called before
1020 // this object gets deleted, we also avoid interrupting unnecessary.
1021 deleteLater();
1022}
1023
1024QtCocoaInterruptDispatcher::~QtCocoaInterruptDispatcher()
1025{
1026 if (cancelled)
1027 return;
1028 instance = nullptr;
1029 QCocoaEventDispatcher::instance()->interrupt();
1030}
1031
1033{
1034 if (!instance)
1035 return;
1036 instance->cancelled = true;
1037 delete instance;
1038 instance = nullptr;
1039}
1040
1046
1048
DarwinBluetooth::LECBManagerNotifier * notifier
bool cancelled
Definition btgcdtimer.mm:27
static QAbstractEventDispatcher * instance(QThread *thread=nullptr)
Returns a pointer to the event dispatcher object for the specified thread.
bool filterNativeEvent(const QByteArray &eventType, void *message, qintptr *result)
Sends message through the event filters that were set by installNativeEventFilter().
std::chrono::nanoseconds Duration
A {std::chrono::duration} type that is used in various API in this class.
void awake()
This signal is emitted after the event loop returns from a function that could block.
T loadRelaxed() const noexcept
Type loadRelaxed() const noexcept
void beginModalSession(QWindow *widget)
static void postedEventsSourceCallback(void *info)
static void runLoopTimerCallback(CFRunLoopTimerRef, void *info)
static void activateTimersSourceCallback(void *info)
static void waitingObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
QStack< QCocoaModalSessionInfo > cocoaModalSessionStack
QList< TimerInfoV2 > timersForObject(QObject *object) const final
void interrupt()
Interrupts event dispatching.
void unregisterSocketNotifier(QSocketNotifier *notifier)
Unregisters notifier from the event dispatcher.
void registerSocketNotifier(QSocketNotifier *notifier)
Registers notifier with the event loop.
QCocoaEventDispatcher(QAbstractEventDispatcherPrivate &priv, QObject *parent=nullptr)
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
Duration remainingTime(Qt::TimerId timerId) const final
Returns the remaining time of the timer with the given timerId.
bool unregisterTimer(Qt::TimerId timerId) final
friend void qt_mac_maybeCancelWaitForMoreEventsForwarder(QAbstractEventDispatcher *eventDispatcher)
static void clearCurrentThreadCocoaEventDispatcherInterruptFlag()
bool processEvents(QEventLoop::ProcessEventsFlags flags)
Processes pending events that match flags until there are no more events to process.
QRect geometry() const override
Returns the current geometry of a window.
NSWindow * nativeWindow() const
void setGeometry(const QRect &rect) override
This function is called by Qt whenever a window is moved or resized using the QWindow API.
static void sendPostedEvents(QObject *receiver=nullptr, int event_type=0)
Immediately dispatches all events which have been previously queued with QCoreApplication::postEvent(...
\inmodule QtCore
Definition qeventloop.h:16
@ WaitForMoreEvents
Definition qeventloop.h:29
@ ExcludeUserInputEvents
Definition qeventloop.h:27
bool isEmpty() const noexcept
Definition qlist.h:401
value_type takeFirst()
Definition qlist.h:566
static QObjectPrivate * get(QObject *o)
Definition qobject_p.h:150
QAtomicPointer< QThreadData > threadData
Definition qobject_p.h:203
\inmodule QtCore
Definition qobject.h:103
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1598
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
\inmodule QtCore\reentrant
Definition qrect.h:30
\inmodule QtCore
static QThread * currentThread()
Definition qthread.cpp:1039
bool isEmpty() const
std::optional< Duration > timerWait()
static bool sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
\inmodule QtGui
Definition qwindow.h:63
rect
[4]
QStyleOptionButton opt
Combined button and popup list for selecting options.
TimerType
static void * context
struct _NSModalSession * NSModalSession
static CFRunLoopRef mainRunLoop()
static void qt_mac_waitForMoreEvents(NSString *runLoopMode=NSDefaultRunLoopMode)
static Boolean runLoopSourceEqualCallback(const void *info1, const void *info2)
void qt_mac_maybeCancelWaitForMoreEventsForwarder(QAbstractEventDispatcher *eventDispatcher)
static bool isUserInputEvent(NSEvent *event)
long NSInteger
#define forever
Definition qforeach.h:78
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLint location
GLenum type
GLbitfield flags
struct _cl_event * event
GLhandleARB obj
[2]
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QScopeGuard< typename std::decay< F >::type > qScopeGuard(F &&f)
[qScopeGuard]
Definition qscopeguard.h:60
static bool isRunning()
Definition main.cpp:452
#define emit
QT_BEGIN_NAMESPACE constexpr std::underlying_type_t< Enum > qToUnderlying(Enum e) noexcept
unsigned int uint
Definition qtypes.h:34
QObject::connect nullptr
sem release()
aWidget window() -> setWindowTitle("New Window Title")
[2]
QHostInfo info
[0]