Qt 6.x
The Qt SDK
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
83 d->maybeCancelWaitForMoreEvents();
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 if (!runLoopTimerRef) {
119 // start the CFRunLoopTimer
120 CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
121 CFTimeInterval interval;
122 CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.);
123
124 // Q: when should the CFRunLoopTimer fire for the first time?
125 struct timespec tv;
126 if (timerInfoList.timerWait(tv)) {
127 // A: when we have timers to fire, of course
128 interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001);
129 } else {
130 // this shouldn't really happen, but in case it does, set the timer to fire a some point in the distant future
131 interval = oneyear;
132 }
133
134 ttf += interval;
135 CFRunLoopTimerContext info = { 0, this, nullptr, nullptr, nullptr };
136 // create the timer with a large interval, as recommended by the CFRunLoopTimerSetNextFireDate()
137 // documentation, since we will adjust the timer's time-to-fire as needed to keep Qt timers working
138 runLoopTimerRef = CFRunLoopTimerCreate(nullptr, ttf, oneyear, 0, 0, QCocoaEventDispatcherPrivate::runLoopTimerCallback, &info);
140
141 CFRunLoopAddTimer(mainRunLoop(), runLoopTimerRef, kCFRunLoopCommonModes);
142 } else {
143 // calculate when we need to wake up to process timers again
144 CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
145 CFTimeInterval interval;
146
147 // Q: when should the timer first next?
148 struct timespec tv;
149 if (timerInfoList.timerWait(tv)) {
150 // A: when we have timers to fire, of course
151 interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001);
152 } else {
153 // no timers can fire, but we cannot stop the CFRunLoopTimer, set the timer to fire at some
154 // point in the distant future (the timer interval is one year)
155 interval = CFRunLoopTimerGetInterval(runLoopTimerRef);
156 }
157
158 ttf += interval;
159 CFRunLoopTimerSetNextFireDate(runLoopTimerRef, ttf);
160 }
161}
162
164{
165 if (!runLoopTimerRef)
166 return;
167
168 CFRunLoopTimerInvalidate(runLoopTimerRef);
169 CFRelease(runLoopTimerRef);
170 runLoopTimerRef = nullptr;
171}
172
174{
175#ifndef QT_NO_DEBUG
176 if (timerId < 1 || interval < 0 || !obj) {
177 qWarning("QCocoaEventDispatcher::registerTimer: invalid arguments");
178 return;
179 } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
180 qWarning("QObject::startTimer: timers cannot be started from another thread");
181 return;
182 }
183#endif
184
186 d->timerInfoList.registerTimer(timerId, interval, timerType, obj);
187 d->maybeStartCFRunLoopTimer();
188}
189
191{
192#ifndef QT_NO_DEBUG
193 if (timerId < 1) {
194 qWarning("QCocoaEventDispatcher::unregisterTimer: invalid argument");
195 return false;
196 } else if (thread() != QThread::currentThread()) {
197 qWarning("QObject::killTimer: timers cannot be stopped from another thread");
198 return false;
199 }
200#endif
201
203 bool returnValue = d->timerInfoList.unregisterTimer(timerId);
204 if (!d->timerInfoList.isEmpty())
205 d->maybeStartCFRunLoopTimer();
206 else
207 d->maybeStopCFRunLoopTimer();
208 return returnValue;
209}
210
212{
213#ifndef QT_NO_DEBUG
214 if (!obj) {
215 qWarning("QCocoaEventDispatcher::unregisterTimers: invalid argument");
216 return false;
217 } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
218 qWarning("QObject::killTimers: timers cannot be stopped from another thread");
219 return false;
220 }
221#endif
222
224 bool returnValue = d->timerInfoList.unregisterTimers(obj);
225 if (!d->timerInfoList.isEmpty())
226 d->maybeStartCFRunLoopTimer();
227 else
228 d->maybeStopCFRunLoopTimer();
229 return returnValue;
230}
231
234{
235#ifndef QT_NO_DEBUG
236 if (!object) {
237 qWarning("QCocoaEventDispatcher:registeredTimers: invalid argument");
238 return QList<TimerInfo>();
239 }
240#endif
241
242 Q_D(const QCocoaEventDispatcher);
243 return d->timerInfoList.registeredTimers(object);
244}
245
246/*
247 Register a QSocketNotifier with the mac event system by creating a CFSocket with
248 with a read/write callback.
249
250 Qt has separate socket notifiers for reading and writing, but on the mac there is
251 a limitation of one CFSocket object for each native socket.
252*/
254{
256 d->cfSocketNotifier.registerSocketNotifier(notifier);
257}
258
260{
262 d->cfSocketNotifier.unregisterSocketNotifier(notifier);
263}
264
265static bool isUserInputEvent(NSEvent* event)
266{
267 switch ([event type]) {
268 case NSEventTypeLeftMouseDown:
269 case NSEventTypeLeftMouseUp:
270 case NSEventTypeRightMouseDown:
271 case NSEventTypeRightMouseUp:
272 case NSEventTypeMouseMoved: // ??
273 case NSEventTypeLeftMouseDragged:
274 case NSEventTypeRightMouseDragged:
275 case NSEventTypeMouseEntered:
276 case NSEventTypeMouseExited:
277 case NSEventTypeKeyDown:
278 case NSEventTypeKeyUp:
279 case NSEventTypeFlagsChanged: // key modifiers changed?
280 case NSEventTypeCursorUpdate: // ??
281 case NSEventTypeScrollWheel:
282 case NSEventTypeTabletPoint:
283 case NSEventTypeTabletProximity:
284 case NSEventTypeOtherMouseDown:
285 case NSEventTypeOtherMouseUp:
286 case NSEventTypeOtherMouseDragged:
287#ifndef QT_NO_GESTURES
288 case NSEventTypeGesture: // touch events
289 case NSEventTypeMagnify:
290 case NSEventTypeSwipe:
291 case NSEventTypeRotate:
292 case NSEventTypeBeginGesture:
293 case NSEventTypeEndGesture:
294#endif // QT_NO_GESTURES
295 return true;
296 break;
297 default:
298 break;
299 }
300 return false;
301}
302
303static inline void qt_mac_waitForMoreEvents(NSString *runLoopMode = NSDefaultRunLoopMode)
304{
305 // If no event exist in the cocoa event que, wait (and free up cpu time) until
306 // at least one event occur. Setting 'dequeuing' to 'no' in the following call
307 // causes it to hang under certain circumstances (QTBUG-28283), so we tell it
308 // to dequeue instead, just to repost the event again:
309 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
310 untilDate:[NSDate distantFuture]
311 inMode:runLoopMode
312 dequeue:YES];
313 if (event)
314 [NSApp postEvent:event atStart:YES];
315}
316
317bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
318{
320
321 // In rare rather corner cases a user's application messes with
322 // QEventLoop::exec()/exit() and QCoreApplication::processEvents(),
323 // we have to undo what bool blocker normally does.
324 d->propagateInterrupt = false;
325 const auto boolBlockerUndo = qScopeGuard([d](){
326 if (d->propagateInterrupt)
327 d->interrupt = true;
328 d->propagateInterrupt = false;
329 });
330 QBoolBlocker interruptBlocker(d->interrupt, false);
331
332 bool interruptLater = false;
334
335 emit awake();
336
337 uint oldflags = d->processEventsFlags;
338 d->processEventsFlags = flags;
339
340 // Used to determine whether any eventloop has been exec'ed, and allow posted
341 // and timer events to be processed even if this function has never been called
342 // instead of being kept on hold for the next run of processEvents().
343 ++d->processEventsCalled;
344
345 bool excludeUserEvents = d->processEventsFlags & QEventLoop::ExcludeUserInputEvents;
346 bool retVal = false;
347 forever {
348 if (d->interrupt)
349 break;
350
352 NSEvent* event = nil;
353
354 // First, send all previously excluded input events, if any:
355 if (d->sendQueuedUserInputEvents())
356 retVal = true;
357
358
359 // If Qt is used as a plugin, or as an extension in a native cocoa
360 // application, we should not run or stop NSApplication; This will be
361 // done from the application itself. And if processEvents is called
362 // manually (rather than from a QEventLoop), we cannot enter a tight
363 // loop and block this call, but instead we need to return after one flush.
364 // Finally, if we are to exclude user input events, we cannot call [NSApp run]
365 // as we then loose control over which events gets dispatched:
366 const bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning];
367 const bool canExec_Qt = (!excludeUserEvents
368 && ((d->processEventsFlags & QEventLoop::DialogExec)
369 || (d->processEventsFlags & QEventLoop::EventLoopExec)));
370
371 if (canExec_Qt && canExec_3rdParty) {
372 // We can use exec-mode, meaning that we can stay in a tight loop until
373 // interrupted. This is mostly an optimization, but it allow us to use
374 // [NSApp run], which is the normal code path for cocoa applications.
375 if (NSModalSession session = d->currentModalSession()) {
376 QBoolBlocker execGuard(d->currentExecIsNSAppRun, false);
377 while ([NSApp runModalSession:session] == NSModalResponseContinue && !d->interrupt) {
378 qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode);
379 if (session != d->currentModalSessionCached) {
380 // It's possible to release the current modal session
381 // while we are in this loop, for example, by closing all
382 // windows from a slot via QApplication::closeAllWindows.
383 // In this case we cannot use 'session' anymore. A warning
384 // from Cocoa is: "Use of freed session detected. Do not
385 // call runModalSession: after calling endModalSesion:."
386 break;
387 }
388 }
389
390 if (!d->interrupt && session == d->currentModalSessionCached) {
391 // Someone called [NSApp stopModal:] from outside the event
392 // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
393 // 'session' as well. As a result, we need to restart all internal sessions:
394 d->temporarilyStopAllModalSessions();
395 }
396
397 // Clean up the modal session list, call endModalSession.
398 if (d->cleanupModalSessionsNeeded)
399 d->cleanupModalSessions();
400
401 } else {
402 d->nsAppRunCalledByQt = true;
403 QBoolBlocker execGuard(d->currentExecIsNSAppRun, true);
404 [NSApp run];
405 }
406 retVal = true;
407 } else {
408 int lastSerialCopy = d->lastSerial;
409 const bool hadModalSession = d->currentModalSessionCached;
410 // We cannot block the thread (and run in a tight loop).
411 // Instead we will process all current pending events and return.
412 d->ensureNSAppInitialized();
413 if (NSModalSession session = d->currentModalSession()) {
414 // INVARIANT: a modal window is executing.
415 if (!excludeUserEvents) {
416 // Since we can dispatch all kinds of events, we choose
417 // to use cocoa's native way of running modal sessions:
419 qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode);
420 NSInteger status = [NSApp runModalSession:session];
421 if (status != NSModalResponseContinue && session == d->currentModalSessionCached) {
422 // INVARIANT: Someone called [NSApp stopModal:] from outside the event
423 // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
424 // 'session' as well. As a result, we need to restart all internal sessions:
425 d->temporarilyStopAllModalSessions();
426 }
427
428 // Clean up the modal session list, call endModalSession.
429 if (d->cleanupModalSessionsNeeded)
430 d->cleanupModalSessions();
431
432 retVal = true;
433 } else do {
434 // Dispatch all non-user events (but que non-user events up for later). In
435 // this case, we need more control over which events gets dispatched, and
436 // cannot use [NSApp runModalSession:session]:
437 event = [NSApp nextEventMatchingMask:NSEventMaskAny
438 untilDate:nil
439 inMode:NSModalPanelRunLoopMode
440 dequeue: YES];
441
442 if (event) {
443 if (isUserInputEvent(event)) {
444 [event retain];
445 d->queuedUserInputEvents.append(event);
446 continue;
447 }
448 if (!filterNativeEvent("NSEvent", event, nullptr)) {
449 [NSApp sendEvent:event];
450 retVal = true;
451 }
452 }
453 } while (!d->interrupt && event);
454 } else do {
455 // INVARIANT: No modal window is executing.
456 event = [NSApp nextEventMatchingMask:NSEventMaskAny
457 untilDate:nil
458 inMode:NSDefaultRunLoopMode
459 dequeue: YES];
460
461 if (event) {
463 if (isUserInputEvent(event)) {
464 [event retain];
465 d->queuedUserInputEvents.append(event);
466 continue;
467 }
468 }
469 if (!filterNativeEvent("NSEvent", event, nullptr)) {
470 [NSApp sendEvent:event];
471 retVal = true;
472 }
473 }
474
475 // Clean up the modal session list, call endModalSession.
476 if (d->cleanupModalSessionsNeeded)
477 d->cleanupModalSessions();
478
479 } while (!d->interrupt && event);
480
481 if ((d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
482 // When called "manually", always process posted events and timers
483 bool oldInterrupt = d->interrupt;
484 d->processPostedEvents();
485 if (!oldInterrupt && d->interrupt && !d->currentModalSession()) {
486 // We had direct processEvent call, coming not from QEventLoop::exec().
487 // One of the posted events triggered an application to interrupt the loop.
488 // But bool blocker will reset d->interrupt to false, so the real event
489 // loop will never notice it was interrupted. Now we'll have to fix it by
490 // enforcing the value of d->interrupt.
491 d->propagateInterrupt = true;
492 }
493 retVal = d->processTimers() || retVal;
494 }
495
496 // be sure to return true if the posted event source fired
497 retVal = retVal || lastSerialCopy != d->lastSerial;
498
499 // Since the window that holds modality might have changed while processing
500 // events, we we need to interrupt when we return back the previous process
501 // event recursion to ensure that we spin the correct modal session.
502 // We do the interruptLater at the end of the function to ensure that we don't
503 // disturb the 'wait for more events' below (as deleteLater will post an event):
504 if (hadModalSession && !d->currentModalSessionCached)
505 interruptLater = true;
506 }
507 bool canWait = (d->threadData.loadRelaxed()->canWait
508 && !retVal
509 && !d->interrupt
510 && (d->processEventsFlags & QEventLoop::WaitForMoreEvents));
511 if (canWait) {
512 // INVARIANT: We haven't processed any events yet. And we're told
513 // to stay inside this function until at least one event is processed.
515 d->processEventsFlags &= ~QEventLoop::WaitForMoreEvents;
516 } else {
517 // Done with event processing for now.
518 // Leave the function:
519 break;
520 }
521 }
522
523 d->processEventsFlags = oldflags;
524 --d->processEventsCalled;
525
526 // If we're interrupted, we need to interrupt the _current_
527 // recursion as well to check if it is still supposed to be
528 // executing. This way we wind down the stack until we land
529 // on a recursion that again calls processEvents (typically
530 // from QEventLoop), and set interrupt to false:
531 if (d->interrupt)
532 interrupt();
533
534 if (interruptLater)
536
537 return retVal;
538}
539
541{
542#ifndef QT_NO_DEBUG
543 if (timerId < 1) {
544 qWarning("QCocoaEventDispatcher::remainingTime: invalid argument");
545 return -1;
546 }
547#endif
548
550 return d->timerInfoList.timerRemainingTime(timerId);
551}
552
554{
556 d->serialNumber.ref();
557 CFRunLoopSourceSignal(d->postedEventsSource);
558 CFRunLoopWakeUp(mainRunLoop());
559}
560
561/*****************************************************************************
562 QEventDispatcherMac Implementation
563 *****************************************************************************/
564
566{
567 // Some elements in Cocoa require NSApplication to be initialized before
568 // use, for example the menu bar. Under normal circumstances this happens
569 // as part of [NSApp run], as a result of a call to QGuiApplication:exec(),
570 // but in the cases where a dialog is asked to execute before that happens,
571 // or the application spins the event loop manually via processEvents(),
572 // we need to explicitly ensure NSApplication initialization.
573
574 // We can unfortunately not do this via NSApplicationLoad(), as the function
575 // bails out early if there's already an NSApplication instance, which is
576 // the case if any code has called [NSApplication sharedApplication],
577 // or its short form 'NSApp'.
578
579 // Instead we do an actual [NSApp run], but stop the application as soon
580 // as possible, ensuring that AppKit will do the required initialization,
581 // including calling [NSApplication finishLaunching].
582
583 // We only apply this trick at most once for any application, and we avoid
584 // doing it for the common case where main just starts QGuiApplication::exec.
585 if (nsAppRunCalledByQt || [NSApp isRunning])
586 return;
587
588 qCDebug(lcEventDispatcher) << "Ensuring NSApplication is initialized";
589 nsAppRunCalledByQt = true;
590
591 // Stopping the application will still process runloop sources before
592 // actually stopping, so we need to explicitly guard our sources from
593 // doing anything, deferring their actions until later.
594 QBoolBlocker initializationGuard(initializingNSApplication, true);
595
596 CFRunLoopPerformBlock(mainRunLoop(), kCFRunLoopCommonModes, ^{
597 qCDebug(lcEventDispatcher) << "NSApplication has been initialized; Stopping NSApp";
598 [NSApp stop:NSApp];
599 cancelWaitForMoreEvents(); // Post event that wakes up the runloop
600 });
601 [NSApp run];
602 qCDebug(lcEventDispatcher) << "Finished ensuring NSApplication is initialized";
603}
604
606{
607 // Flush, and Stop, all created modal session, and as
608 // such, make them pending again. The next call to
609 // currentModalSession will recreate them again. The
610 // reason to stop all session like this is that otherwise
611 // a call [NSApp stop] would not stop NSApp, but rather
612 // the current modal session. So if we need to stop NSApp
613 // we need to stop all the modal session first. To avoid changing
614 // the stacking order of the windows while doing so, we put
615 // up a block that is used in QCocoaWindow and QCocoaPanel:
616 int stackSize = cocoaModalSessionStack.size();
617 for (int i=0; i<stackSize; ++i) {
619 if (info.session) {
620 [NSApp endModalSession:info.session];
621 info.session = nullptr;
622 [(NSWindow*) info.nswindow release];
623 }
624 }
626}
627
629{
630 // If we have one or more modal windows, this function will create
631 // a session for each of those, and return the one for the top.
634
635 if (cocoaModalSessionStack.isEmpty())
636 return nullptr;
637
638 int sessionCount = cocoaModalSessionStack.size();
639 for (int i=0; i<sessionCount; ++i) {
641 if (!info.window)
642 continue;
643
644 if (!info.session) {
646 QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(info.window->handle());
647 if (!cocoaWindow)
648 continue;
649 NSWindow *nswindow = cocoaWindow->nativeWindow();
650 if (!nswindow)
651 continue;
652
655 info.nswindow = nswindow;
656 [(NSWindow*) info.nswindow retain];
657 QRect rect = cocoaWindow->geometry();
658 info.session = [NSApp beginModalSessionForWindow:nswindow];
659
660 // The call to beginModalSessionForWindow above processes events and may
661 // have deleted or destroyed the window. Check if it's still valid.
662 if (!info.window)
663 continue;
664 cocoaWindow = static_cast<QCocoaWindow *>(info.window->handle());
665 if (!cocoaWindow)
666 continue;
667
668 if (rect != cocoaWindow->geometry())
669 cocoaWindow->setGeometry(rect);
670 }
673 }
675}
676
678{
679 return !cocoaModalSessionStack.isEmpty();
680}
681
683{
684 // Go through the list of modal sessions, and end those
685 // that no longer has a window associated; no window means
686 // the session has logically ended. The reason we wait like
687 // this to actually end the sessions for real (rather than at the
688 // point they were marked as stopped), is that ending a session
689 // when no other session runs below it on the stack will make cocoa
690 // drop some events on the floor.
692 int stackSize = cocoaModalSessionStack.size();
693
694 for (int i=stackSize-1; i>=0; --i) {
696 if (info.window) {
697 // This session has a window, and is therefore not marked
698 // as stopped. So just make it current. There might still be other
699 // stopped sessions on the stack, but those will be stopped on
700 // a later "cleanup" call.
702 break;
703 }
705 if (info.session) {
706 Q_ASSERT(info.nswindow);
707 [NSApp endModalSession:info.session];
708 [(NSWindow *)info.nswindow release];
709 }
710 // remove the info now that we are finished with it
712 }
713
715}
716
718{
719 // We need to start spinning the modal session. Usually this is done with
720 // QDialog::exec() for Qt Widgets based applications, but for others that
721 // just call show(), we need to interrupt().
723 q->interrupt();
724
725 // Add a new, empty (null), NSModalSession to the stack.
726 // It will become active the next time QEventDispatcher::processEvents is called.
727 // A QCocoaModalSessionInfo is considered pending to become active if the window pointer
728 // is non-zero, and the session pointer is zero (it will become active upon a call to
729 // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if
730 // the window pointer is zero, and the session pointer is non-zero (it will be fully
731 // stopped in cleanupModalSessions()).
732 QCocoaModalSessionInfo info = {window, nullptr, nullptr};
735}
736
738{
740
741 // Mark all sessions attached to window as pending to be stopped. We do this
742 // by setting the window pointer to zero, but leave the session pointer.
743 // We don't tell cocoa to stop any sessions just yet, because cocoa only understands
744 // when we stop the _current_ modal session (which is the session on top of
745 // the stack, and might not belong to 'window').
746 int stackSize = cocoaModalSessionStack.size();
747 int endedSessions = 0;
748 for (int i=stackSize-1; i>=0; --i) {
750 if (!info.window)
751 endedSessions++;
752 if (info.window == window) {
753 info.window = nullptr;
754 if (i + endedSessions == stackSize-1) {
755 // The top sessions ended. Interrupt the event dispatcher to
756 // start spinning the correct session immediately.
757 q->interrupt();
760 }
761 }
762 }
763}
764
766 : processEventsFlags(0),
767 runLoopTimerRef(nullptr),
768 blockSendPostedEvents(false),
769 currentExecIsNSAppRun(false),
770 nsAppRunCalledByQt(false),
771 cleanupModalSessionsNeeded(false),
772 processEventsCalled(0),
773 currentModalSessionCached(nullptr),
774 lastSerial(-1),
775 interrupt(false)
776{
777}
778
780{
781 static_cast<QCocoaEventDispatcher *>(eventDispatcher)->d_func()->maybeCancelWaitForMoreEvents();
782}
783
786{
788
789 d->cfSocketNotifier.setHostEventDispatcher(this);
790 d->cfSocketNotifier.setMaybeCancelWaitForMoreEventsCallback(qt_mac_maybeCancelWaitForMoreEventsForwarder);
791
792 // keep our sources running when modal loops are running
793 CFRunLoopAddCommonMode(mainRunLoop(), (CFStringRef) NSModalPanelRunLoopMode);
794
795 CFRunLoopSourceContext context;
796 bzero(&context, sizeof(CFRunLoopSourceContext));
797 context.info = d;
799
800 // source used to activate timers
802 d->activateTimersSourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
803 Q_ASSERT(d->activateTimersSourceRef);
804 CFRunLoopAddSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
805
806 // source used to send posted events
808 d->postedEventsSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
809 Q_ASSERT(d->postedEventsSource);
810 CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
811
812 // observer to emit aboutToBlock() and awake()
813 CFRunLoopObserverContext observerContext;
814 bzero(&observerContext, sizeof(CFRunLoopObserverContext));
815 observerContext.info = this;
816 d->waitingObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
817 kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting,
818 true, 0,
820 &observerContext);
821 CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes);
822}
823
825 CFRunLoopActivity activity, void *info)
826{
827 if (activity == kCFRunLoopBeforeWaiting)
828 emit static_cast<QCocoaEventDispatcher*>(info)->aboutToBlock();
829 else
830 emit static_cast<QCocoaEventDispatcher*>(info)->awake();
831}
832
834{
837 return false;
838 bool didSendEvent = false;
839 while (!queuedUserInputEvents.isEmpty()) {
840 NSEvent *event = static_cast<NSEvent *>(queuedUserInputEvents.takeFirst());
841 if (!q->filterNativeEvent("NSEvent", event, nullptr)) {
842 [NSApp sendEvent:event];
843 didSendEvent = true;
844 }
845 [event release];
846 }
847 return didSendEvent;
848}
849
851{
853 // We're told to not send posted events (because the event dispatcher
854 // is currently working on setting up the correct session to run). But
855 // we still need to make sure that we don't fall asleep until pending events
856 // are sendt, so we just signal this need, and return:
857 CFRunLoopSourceSignal(postedEventsSource);
858 return;
859 }
860
863
864 if (processEventsCalled > 0 && interrupt) {
866 // The event dispatcher has been interrupted. But since
867 // [NSApplication run] is running the event loop, we
868 // delayed stopping it until now (to let cocoa process
869 // pending cocoa events first).
872 [NSApp stop:NSApp];
874 }
875 return;
876 }
877
878 int serial = serialNumber.loadRelaxed();
879 if (!threadData.loadRelaxed()->canWait || (serial != lastSerial)) {
880 lastSerial = serial;
883 }
884}
885
887{
889 if (d->initializingNSApplication) {
890 qCDebug(lcEventDispatcher) << "Deferring" << __FUNCTION__ << "due to NSApp initialization";
891 // We don't want to process any sources during explicit NSApplication
892 // initialization, so defer the source until the actual event processing.
893 CFRunLoopSourceSignal(d->postedEventsSource);
894 return;
895 }
896
897 if (d->processEventsCalled && (d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
898 // processEvents() was called "manually," ignore this source for now
899 d->maybeCancelWaitForMoreEvents();
900 return;
901 }
902 d->sendQueuedUserInputEvents();
903 d->processPostedEvents();
904 d->maybeCancelWaitForMoreEvents();
905}
906
908{
909 // In case the event dispatcher is waiting for more
910 // events somewhere, we post a dummy event to wake it up:
912 [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined location:NSZeroPoint
913 modifierFlags:0 timestamp:0. windowNumber:0 context:nil
914 subtype:QtCocoaEventSubTypeWakeup data1:0 data2:0] atStart:NO];
915}
916
918{
920 // RunLoop sources are not NSEvents, but they do generate Qt events. If
921 // WaitForMoreEvents was set, but EventLoopExec is not, processEvents()
922 // should return after a source has sent some Qt events.
924 }
925}
926
928{
930 d->interrupt = true;
931 wakeUp();
932
933 // We do nothing more here than setting d->interrupt = true, and
934 // poke the event loop if it is sleeping. Actually stopping
935 // NSApp, or the current modal session, is done inside the send
936 // posted events callback. We do this to ensure that all current pending
937 // cocoa events gets delivered before we stop. Otherwise, if we now stop
938 // the last event loop recursion, cocoa will just drop pending posted
939 // events on the floor before we get a chance to reestablish a new session.
940 d->cancelWaitForMoreEvents();
941}
942
943// QTBUG-56746: The behavior of processEvents() has been changed to not clear
944// the interrupt flag. Use this function to clear it.
946{
947 QCocoaEventDispatcher *cocoaEventDispatcher =
948 qobject_cast<QCocoaEventDispatcher *>(QThread::currentThread()->eventDispatcher());
949 if (!cocoaEventDispatcher)
950 return;
951 QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate =
952 static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher));
953 cocoaEventDispatcherPrivate->interrupt = false;
954}
955
957{
959
960 qDeleteAll(d->timerInfoList);
961 d->maybeStopCFRunLoopTimer();
962 CFRunLoopRemoveSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
963 CFRelease(d->activateTimersSourceRef);
964
965 // end all modal sessions
966 for (int i = 0; i < d->cocoaModalSessionStack.count(); ++i) {
967 QCocoaModalSessionInfo &info = d->cocoaModalSessionStack[i];
968 if (info.session) {
969 [NSApp endModalSession:info.session];
970 [(NSWindow *)info.nswindow release];
971 }
972 }
973
974 // release all queued user input events
975 for (int i = 0; i < d->queuedUserInputEvents.count(); ++i) {
976 NSEvent *nsevent = static_cast<NSEvent *>(d->queuedUserInputEvents.at(i));
977 [nsevent release];
978 }
979
980 d->cfSocketNotifier.removeSocketNotifiers();
981
982 CFRunLoopRemoveSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
983 CFRelease(d->postedEventsSource);
984
985 CFRunLoopObserverInvalidate(d->waitingObserver);
986 CFRelease(d->waitingObserver);
987}
988
989QtCocoaInterruptDispatcher* QtCocoaInterruptDispatcher::instance = nullptr;
990
991QtCocoaInterruptDispatcher::QtCocoaInterruptDispatcher() : cancelled(false)
992{
993 // The whole point of this class is that we enable a way to interrupt
994 // the event dispatcher when returning back to a lower recursion level
995 // than where interruptLater was called. This is needed to detect if
996 // [NSApp run] should still be running at the recursion level it is at.
997 // Since the interrupt is canceled if processEvents is called before
998 // this object gets deleted, we also avoid interrupting unnecessary.
999 deleteLater();
1000}
1001
1002QtCocoaInterruptDispatcher::~QtCocoaInterruptDispatcher()
1003{
1004 if (cancelled)
1005 return;
1006 instance = nullptr;
1008}
1009
1011{
1012 if (!instance)
1013 return;
1014 instance->cancelled = true;
1015 delete instance;
1016 instance = nullptr;
1017}
1018
1020{
1022 instance = new QtCocoaInterruptDispatcher;
1023}
1024
1026
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().
void awake()
This signal is emitted after the event loop returns from a function that could block.
virtual void interrupt()=0
Interrupts event dispatching.
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
void registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object)
Register a timer with the specified timerId, interval, and timerType for the given object.
void interrupt()
Interrupts event dispatching.
void unregisterSocketNotifier(QSocketNotifier *notifier)
Unregisters notifier from the event dispatcher.
bool unregisterTimer(int timerId)
Unregisters the timer with the given timerId.
int remainingTime(int timerId)
Returns the remaining time in milliseconds with the given timerId.
void registerSocketNotifier(QSocketNotifier *notifier)
Registers notifier with the event loop.
QList< TimerInfo > registeredTimers(QObject *object) const
Returns a list of registered timers for object.
QCocoaEventDispatcher(QAbstractEventDispatcherPrivate &priv, QObject *parent=nullptr)
bool unregisterTimers(QObject *object)
Unregisters all the timers associated with the given object.
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
Definition qlist.h:74
bool isEmpty() const noexcept
Definition qlist.h:390
value_type takeFirst()
Definition qlist.h:549
static QObjectPrivate * get(QObject *o)
Definition qobject_p.h:153
QAtomicPointer< QThreadData > threadData
Definition qobject_p.h:202
\inmodule QtCore
Definition qobject.h:90
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1561
void deleteLater()
\threadsafe
Definition qobject.cpp:2352
\inmodule QtCore\reentrant
Definition qrect.h:30
\inmodule QtCore
static QThread * currentThread()
Definition qthread.cpp:966
bool timerWait(timespec &)
static bool sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
\inmodule QtGui
Definition qwindow.h:63
qDeleteAll(list.begin(), list.end())
rect
[4]
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:162
#define Q_LOGGING_CATEGORY(name,...)
#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:358
#define emit
unsigned int uint
Definition qtypes.h:29
long long qint64
Definition qtypes.h:55
QFileInfo info(fileName)
[8]
QObject::connect nullptr
sem release()
aWidget window() -> setWindowTitle("New Window Title")
[2]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent