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