source: trunk/src/gui/kernel/qeventdispatcher_mac.mm

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 44.9 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42/****************************************************************************
43**
44** Copyright (c) 2007-2008, Apple, Inc.
45**
46** All rights reserved.
47**
48** Redistribution and use in source and binary forms, with or without
49** modification, are permitted provided that the following conditions are met:
50**
51** * Redistributions of source code must retain the above copyright notice,
52** this list of conditions and the following disclaimer.
53**
54** * Redistributions in binary form must reproduce the above copyright notice,
55** this list of conditions and the following disclaimer in the documentation
56** and/or other materials provided with the distribution.
57**
58** * Neither the name of Apple, Inc. nor the names of its contributors
59** may be used to endorse or promote products derived from this software
60** without specific prior written permission.
61**
62** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
63** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
64** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
65** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
66** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
67** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
68** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
69** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
70** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
71** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
72** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
73**
74****************************************************************************/
75
76#include "qplatformdefs.h"
77#include "private/qt_mac_p.h"
78#include "qeventdispatcher_mac_p.h"
79#include "qapplication.h"
80#include "qevent.h"
81#include "qdialog.h"
82#include "qhash.h"
83#include "qsocketnotifier.h"
84#include "private/qwidget_p.h"
85#include "private/qthread_p.h"
86#include "private/qapplication_p.h"
87
88#include <private/qcocoaapplication_mac_p.h>
89#include "private/qt_cocoa_helpers_mac_p.h"
90
91#ifndef QT_NO_THREAD
92# include "qmutex.h"
93#endif
94
95QT_BEGIN_NAMESPACE
96
97QT_USE_NAMESPACE
98
99/*****************************************************************************
100 Externals
101 *****************************************************************************/
102extern void qt_event_request_timer(MacTimerInfo *); //qapplication_mac.cpp
103extern MacTimerInfo *qt_event_get_timer(EventRef); //qapplication_mac.cpp
104extern void qt_event_request_select(QEventDispatcherMac *); //qapplication_mac.cpp
105extern void qt_event_request_updates(); //qapplication_mac.cpp
106extern OSWindowRef qt_mac_window_for(const QWidget *); //qwidget_mac.cpp
107extern bool qt_is_gui_used; //qapplication.cpp
108extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); // qapplication.cpp
109extern bool qt_mac_is_macsheet(const QWidget *); //qwidget_mac.cpp
110
111static inline CFRunLoopRef mainRunLoop()
112{
113#ifndef QT_MAC_USE_COCOA
114 return reinterpret_cast<CFRunLoopRef>(const_cast<void *>(GetCFRunLoopFromEventLoop(GetMainEventLoop())));
115#else
116 return CFRunLoopGetMain();
117#endif
118}
119
120/*****************************************************************************
121 Timers stuff
122 *****************************************************************************/
123
124/* timer call back */
125void QEventDispatcherMacPrivate::activateTimer(CFRunLoopTimerRef, void *info)
126{
127 int timerID =
128#ifdef Q_OS_MAC64
129 qint64(info);
130#else
131 int(info);
132#endif
133
134 MacTimerInfo *tmr;
135 tmr = macTimerHash.value(timerID);
136 if (tmr == 0 || tmr->pending == true)
137 return; // Can't send another timer event if it's pending.
138
139
140 if (blockSendPostedEvents) {
141 QCoreApplication::postEvent(tmr->obj, new QTimerEvent(tmr->id));
142 } else {
143 tmr->pending = true;
144 QTimerEvent e(tmr->id);
145 qt_sendSpontaneousEvent(tmr->obj, &e);
146 // Get the value again in case the timer gets unregistered during the sendEvent.
147 tmr = macTimerHash.value(timerID);
148 if (tmr != 0)
149 tmr->pending = false;
150 }
151
152}
153
154void QEventDispatcherMac::registerTimer(int timerId, int interval, QObject *obj)
155{
156#ifndef QT_NO_DEBUG
157 if (timerId < 1 || interval < 0 || !obj) {
158 qWarning("QEventDispatcherMac::registerTimer: invalid arguments");
159 return;
160 } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
161 qWarning("QObject::startTimer: timers cannot be started from another thread");
162 return;
163 }
164#endif
165
166 MacTimerInfo *t = new MacTimerInfo();
167 t->id = timerId;
168 t->interval = interval;
169 t->obj = obj;
170 t->runLoopTimer = 0;
171 t->pending = false;
172
173 CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent();
174 CFTimeInterval cfinterval = qMax(CFTimeInterval(interval) / 1000, 0.0000001);
175 fireDate += cfinterval;
176 QEventDispatcherMacPrivate::macTimerHash.insert(timerId, t);
177 CFRunLoopTimerContext info = { 0, (void *)timerId, 0, 0, 0 };
178 t->runLoopTimer = CFRunLoopTimerCreate(0, fireDate, cfinterval, 0, 0,
179 QEventDispatcherMacPrivate::activateTimer, &info);
180 if (t->runLoopTimer == 0) {
181 qFatal("QEventDispatcherMac::registerTimer: Cannot create timer");
182 }
183 CFRunLoopAddTimer(mainRunLoop(), t->runLoopTimer, kCFRunLoopCommonModes);
184}
185
186bool QEventDispatcherMac::unregisterTimer(int identifier)
187{
188#ifndef QT_NO_DEBUG
189 if (identifier < 1) {
190 qWarning("QEventDispatcherMac::unregisterTimer: invalid argument");
191 return false;
192 } else if (thread() != QThread::currentThread()) {
193 qWarning("QObject::killTimer: timers cannot be stopped from another thread");
194 return false;
195 }
196#endif
197 if (identifier <= 0)
198 return false; // not init'd or invalid timer
199
200 MacTimerInfo *timerInfo = QEventDispatcherMacPrivate::macTimerHash.take(identifier);
201 if (timerInfo == 0)
202 return false;
203
204 if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent)
205 QAbstractEventDispatcherPrivate::releaseTimerId(identifier);
206 CFRunLoopTimerInvalidate(timerInfo->runLoopTimer);
207 CFRelease(timerInfo->runLoopTimer);
208 delete timerInfo;
209
210 return true;
211}
212
213bool QEventDispatcherMac::unregisterTimers(QObject *obj)
214{
215#ifndef QT_NO_DEBUG
216 if (!obj) {
217 qWarning("QEventDispatcherMac::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
225 MacTimerHash::iterator it = QEventDispatcherMacPrivate::macTimerHash.begin();
226 while (it != QEventDispatcherMacPrivate::macTimerHash.end()) {
227 MacTimerInfo *timerInfo = it.value();
228 if (timerInfo->obj != obj) {
229 ++it;
230 } else {
231 if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent)
232 QAbstractEventDispatcherPrivate::releaseTimerId(timerInfo->id);
233 CFRunLoopTimerInvalidate(timerInfo->runLoopTimer);
234 CFRelease(timerInfo->runLoopTimer);
235 delete timerInfo;
236 it = QEventDispatcherMacPrivate::macTimerHash.erase(it);
237 }
238 }
239 return true;
240}
241
242QList<QEventDispatcherMac::TimerInfo>
243QEventDispatcherMac::registeredTimers(QObject *object) const
244{
245 if (!object) {
246 qWarning("QEventDispatcherMac:registeredTimers: invalid argument");
247 return QList<TimerInfo>();
248 }
249
250 QList<TimerInfo> list;
251
252 MacTimerHash::const_iterator it = QEventDispatcherMacPrivate::macTimerHash.constBegin();
253 while (it != QEventDispatcherMacPrivate::macTimerHash.constEnd()) {
254 MacTimerInfo *t = it.value();
255 if (t->obj == object)
256 list << TimerInfo(t->id, t->interval);
257 ++it;
258 }
259 return list;
260}
261
262/**************************************************************************
263 Socket Notifiers
264 *************************************************************************/
265void qt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef,
266 const void *, void *info) {
267 QEventDispatcherMacPrivate *const eventDispatcher
268 = static_cast<QEventDispatcherMacPrivate *>(info);
269 int nativeSocket = CFSocketGetNative(s);
270 MacSocketInfo *socketInfo = eventDispatcher->macSockets.value(nativeSocket);
271 QEvent notifierEvent(QEvent::SockAct);
272
273 // There is a race condition that happen where we disable the notifier and
274 // the kernel still has a notification to pass on. We then get this
275 // notification after we've successfully disabled the CFSocket, but our Qt
276 // notifier is now gone. The upshot is we have to check the notifier
277 // everytime.
278 if (callbackType == kCFSocketReadCallBack) {
279 if (socketInfo->readNotifier)
280 QApplication::sendEvent(socketInfo->readNotifier, &notifierEvent);
281 } else if (callbackType == kCFSocketWriteCallBack) {
282 if (socketInfo->writeNotifier)
283 QApplication::sendEvent(socketInfo->writeNotifier, &notifierEvent);
284 }
285}
286
287/*
288 Adds a loop source for the given socket to the current run loop.
289*/
290CFRunLoopSourceRef qt_mac_add_socket_to_runloop(const CFSocketRef socket)
291{
292 CFRunLoopSourceRef loopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
293 if (!loopSource)
294 return 0;
295
296 CFRunLoopAddSource(mainRunLoop(), loopSource, kCFRunLoopCommonModes);
297 return loopSource;
298}
299
300/*
301 Removes the loop source for the given socket from the current run loop.
302*/
303void qt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop)
304{
305 Q_ASSERT(runloop);
306 CFRunLoopRemoveSource(mainRunLoop(), runloop, kCFRunLoopCommonModes);
307 CFSocketDisableCallBacks(socket, kCFSocketReadCallBack);
308 CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack);
309 CFRunLoopSourceInvalidate(runloop);
310}
311
312/*
313 Register a QSocketNotifier with the mac event system by creating a CFSocket with
314 with a read/write callback.
315
316 Qt has separate socket notifiers for reading and writing, but on the mac there is
317 a limitation of one CFSocket object for each native socket.
318*/
319void QEventDispatcherMac::registerSocketNotifier(QSocketNotifier *notifier)
320{
321 Q_ASSERT(notifier);
322 int nativeSocket = notifier->socket();
323 int type = notifier->type();
324#ifndef QT_NO_DEBUG
325 if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) {
326 qWarning("QSocketNotifier: Internal error");
327 return;
328 } else if (notifier->thread() != thread()
329 || thread() != QThread::currentThread()) {
330 qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread");
331 return;
332 }
333#endif
334
335 Q_D(QEventDispatcherMac);
336
337 if (type == QSocketNotifier::Exception) {
338 qWarning("QSocketNotifier::Exception is not supported on Mac OS X");
339 return;
340 }
341
342 // Check if we have a CFSocket for the native socket, create one if not.
343 MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket);
344 if (!socketInfo) {
345 socketInfo = new MacSocketInfo();
346
347 // Create CFSocket, specify that we want both read and write callbacks (the callbacks
348 // are enabled/disabled later on).
349 const int callbackTypes = kCFSocketReadCallBack | kCFSocketWriteCallBack;
350 CFSocketContext context = {0, d, 0, 0, 0};
351 socketInfo->socket = CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, callbackTypes, qt_mac_socket_callback, &context);
352 if (CFSocketIsValid(socketInfo->socket) == false) {
353 qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to create CFSocket");
354 return;
355 }
356
357 CFOptionFlags flags = CFSocketGetSocketFlags(socketInfo->socket);
358 flags |= kCFSocketAutomaticallyReenableWriteCallBack; //QSocketNotifier stays enabled after a write
359 flags &= ~kCFSocketCloseOnInvalidate; //QSocketNotifier doesn't close the socket upon destruction/invalidation
360 CFSocketSetSocketFlags(socketInfo->socket, flags);
361
362 // Add CFSocket to runloop.
363 if(!(socketInfo->runloop = qt_mac_add_socket_to_runloop(socketInfo->socket))) {
364 qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to add CFSocket to runloop");
365 CFSocketInvalidate(socketInfo->socket);
366 CFRelease(socketInfo->socket);
367 return;
368 }
369
370 // Disable both callback types by default. This must be done after
371 // we add the CFSocket to the runloop, or else these calls will have
372 // no effect.
373 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
374 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
375
376 d->macSockets.insert(nativeSocket, socketInfo);
377 }
378
379 // Increment read/write counters and select enable callbacks if necessary.
380 if (type == QSocketNotifier::Read) {
381 Q_ASSERT(socketInfo->readNotifier == 0);
382 socketInfo->readNotifier = notifier;
383 CFSocketEnableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
384 } else if (type == QSocketNotifier::Write) {
385 Q_ASSERT(socketInfo->writeNotifier == 0);
386 socketInfo->writeNotifier = notifier;
387 CFSocketEnableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
388 }
389}
390
391/*
392 Unregister QSocketNotifer. The CFSocket correspoding to this notifier is
393 removed from the runloop of this is the last notifier that users
394 that CFSocket.
395*/
396void QEventDispatcherMac::unregisterSocketNotifier(QSocketNotifier *notifier)
397{
398 Q_ASSERT(notifier);
399 int nativeSocket = notifier->socket();
400 int type = notifier->type();
401#ifndef QT_NO_DEBUG
402 if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) {
403 qWarning("QSocketNotifier: Internal error");
404 return;
405 } else if (notifier->thread() != thread() || thread() != QThread::currentThread()) {
406 qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread");
407 return;
408 }
409#endif
410
411 Q_D(QEventDispatcherMac);
412
413 if (type == QSocketNotifier::Exception) {
414 qWarning("QSocketNotifier::Exception is not supported on Mac OS X");
415 return;
416 }
417 MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket);
418 if (!socketInfo) {
419 qWarning("QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier");
420 return;
421 }
422
423 // Decrement read/write counters and disable callbacks if necessary.
424 if (type == QSocketNotifier::Read) {
425 Q_ASSERT(notifier == socketInfo->readNotifier);
426 socketInfo->readNotifier = 0;
427 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
428 } else if (type == QSocketNotifier::Write) {
429 Q_ASSERT(notifier == socketInfo->writeNotifier);
430 socketInfo->writeNotifier = 0;
431 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
432 }
433
434 // Remove CFSocket from runloop if this was the last QSocketNotifier.
435 if (socketInfo->readNotifier == 0 && socketInfo->writeNotifier == 0) {
436 if (CFSocketIsValid(socketInfo->socket))
437 qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop);
438 CFRunLoopSourceInvalidate(socketInfo->runloop);
439 CFRelease(socketInfo->runloop);
440 CFSocketInvalidate(socketInfo->socket);
441 CFRelease(socketInfo->socket);
442 delete socketInfo;
443 d->macSockets.remove(nativeSocket);
444 }
445}
446
447bool QEventDispatcherMac::hasPendingEvents()
448{
449 extern uint qGlobalPostedEventsCount();
450 return qGlobalPostedEventsCount() || (qt_is_gui_used && GetNumEventsInQueue(GetMainEventQueue()));
451}
452
453
454static bool qt_mac_send_event(QEventLoop::ProcessEventsFlags, OSEventRef event, OSWindowRef pt)
455{
456#ifndef QT_MAC_USE_COCOA
457 if(pt && SendEventToWindow(event, pt) != eventNotHandledErr)
458 return true;
459 return !SendEventToEventTarget(event, GetEventDispatcherTarget());
460#else // QT_MAC_USE_COCOA
461 if (pt)
462 [pt sendEvent:event];
463 else
464 [NSApp sendEvent:event];
465 return true;
466#endif
467}
468
469#ifdef QT_MAC_USE_COCOA
470static bool IsMouseOrKeyEvent( NSEvent* event )
471{
472 bool result = false;
473
474 switch( [event type] )
475 {
476 case NSLeftMouseDown:
477 case NSLeftMouseUp:
478 case NSRightMouseDown:
479 case NSRightMouseUp:
480 case NSMouseMoved: // ??
481 case NSLeftMouseDragged:
482 case NSRightMouseDragged:
483 case NSMouseEntered:
484 case NSMouseExited:
485 case NSKeyDown:
486 case NSKeyUp:
487 case NSFlagsChanged: // key modifiers changed?
488 case NSCursorUpdate: // ??
489 case NSScrollWheel:
490 case NSTabletPoint:
491 case NSTabletProximity:
492 case NSOtherMouseDown:
493 case NSOtherMouseUp:
494 case NSOtherMouseDragged:
495#ifndef QT_NO_GESTURES
496#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
497 case NSEventTypeGesture: // touch events
498 case NSEventTypeMagnify:
499 case NSEventTypeSwipe:
500 case NSEventTypeRotate:
501 case NSEventTypeBeginGesture:
502 case NSEventTypeEndGesture:
503#endif
504#endif // QT_NO_GESTURES
505 result = true;
506 break;
507
508 default:
509 break;
510 }
511 return result;
512}
513#endif
514
515static inline void qt_mac_waitForMoreEvents()
516{
517#ifndef QT_MAC_USE_COCOA
518 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true) == kCFRunLoopRunTimedOut) ;
519#else
520 // If no event exist in the cocoa event que, wait
521 // (and free up cpu time) until at least one event occur.
522 // This implementation is a bit on the edge, but seems to
523 // work fine:
524 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
525 untilDate:[NSDate distantFuture]
526 inMode:NSDefaultRunLoopMode
527 dequeue:YES];
528 if (event)
529 [NSApp postEvent:event atStart:YES];
530#endif
531}
532
533#ifdef QT_MAC_USE_COCOA
534static inline void qt_mac_waitForMoreModalSessionEvents()
535{
536 // If no event exist in the cocoa event que, wait
537 // (and free up cpu time) until at least one event occur.
538 // This implementation is a bit on the edge, but seems to
539 // work fine:
540 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
541 untilDate:[NSDate distantFuture]
542 inMode:NSModalPanelRunLoopMode
543 dequeue:YES];
544 if (event)
545 [NSApp postEvent:event atStart:YES];
546}
547#endif
548
549bool QEventDispatcherMac::processEvents(QEventLoop::ProcessEventsFlags flags)
550{
551 Q_D(QEventDispatcherMac);
552 d->interrupt = false;
553
554#ifdef QT_MAC_USE_COCOA
555 bool interruptLater = false;
556 QtMacInterruptDispatcherHelp::cancelInterruptLater();
557#endif
558
559 // In case we end up recursing while we now process events, make sure
560 // that we send remaining posted Qt events before this call returns:
561 wakeUp();
562 emit awake();
563
564 bool retVal = false;
565 forever {
566 if (d->interrupt)
567 break;
568
569#ifdef QT_MAC_USE_COCOA
570 QMacCocoaAutoReleasePool pool;
571 NSEvent* event = 0;
572
573 // First, send all previously excluded input events, if any:
574 if (!(flags & QEventLoop::ExcludeUserInputEvents)) {
575 while (!d->queuedUserInputEvents.isEmpty()) {
576 event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst());
577 if (!filterEvent(event)) {
578 qt_mac_send_event(flags, event, 0);
579 retVal = true;
580 }
581 [event release];
582 }
583 }
584
585 // If Qt is used as a plugin, or as an extension in a native cocoa
586 // application, we should not run or stop NSApplication; This will be
587 // done from the application itself. And if processEvents is called
588 // manually (rather than from a QEventLoop), we cannot enter a tight
589 // loop and block this call, but instead we need to return after one flush:
590 const bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning];
591 const bool canExec_Qt = flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec;
592
593 if (canExec_Qt && canExec_3rdParty) {
594 // We can use exec-mode, meaning that we can stay in a tight loop until
595 // interrupted. This is mostly an optimization, but it allow us to use
596 // [NSApp run], which is the normal code path for cocoa applications.
597 if (NSModalSession session = d->currentModalSession()) {
598 QBoolBlocker execGuard(d->currentExecIsNSAppRun, false);
599 while ([NSApp runModalSession:session] == NSRunContinuesResponse && !d->interrupt)
600 qt_mac_waitForMoreModalSessionEvents();
601
602 if (!d->interrupt && session == d->currentModalSessionCached) {
603 // Someone called [NSApp stopModal:] from outside the event
604 // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
605 // 'session' as well. As a result, we need to restart all internal sessions:
606 d->temporarilyStopAllModalSessions();
607 }
608 } else {
609 d->nsAppRunCalledByQt = true;
610 QBoolBlocker execGuard(d->currentExecIsNSAppRun, true);
611 [NSApp run];
612 }
613 retVal = true;
614 } else {
615 // We cannot block the thread (and run in a tight loop).
616 // Instead we will process all current pending events and return.
617 d->ensureNSAppInitialized();
618 if (NSModalSession session = d->currentModalSession()) {
619 if (flags & QEventLoop::WaitForMoreEvents)
620 qt_mac_waitForMoreModalSessionEvents();
621 NSInteger status = [NSApp runModalSession:session];
622 if (status != NSRunContinuesResponse && session == d->currentModalSessionCached) {
623 // INVARIANT: Someone called [NSApp stopModal:] from outside the event
624 // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
625 // 'session' as well. As a result, we need to restart all internal sessions:
626 d->temporarilyStopAllModalSessions();
627 }
628 retVal = true;
629 } else do {
630 event = [NSApp nextEventMatchingMask:NSAnyEventMask
631 untilDate:nil
632 inMode:NSDefaultRunLoopMode
633 dequeue: YES];
634
635 if (event) {
636 if (flags & QEventLoop::ExcludeUserInputEvents) {
637 if (IsMouseOrKeyEvent(event)) {
638 [event retain];
639 d->queuedUserInputEvents.append(event);
640 continue;
641 }
642 }
643 if (!filterEvent(event) && qt_mac_send_event(flags, event, 0))
644 retVal = true;
645 }
646 } while (!d->interrupt && event != nil);
647
648 // Since the window that holds modality might have changed while processing
649 // events, we we need to interrupt when we return back the previous process
650 // event recursion to ensure that we spin the correct modal session.
651 // We do the interruptLater at the end of the function to ensure that we don't
652 // disturb the 'wait for more events' below (as deleteLater will post an event):
653 interruptLater = true;
654 }
655#else
656 do {
657 EventRef event;
658 if (!(flags & QEventLoop::ExcludeUserInputEvents)
659 && !d->queuedUserInputEvents.isEmpty()) {
660 // process a pending user input event
661 event = static_cast<EventRef>(d->queuedUserInputEvents.takeFirst());
662 } else {
663 OSStatus err = ReceiveNextEvent(0,0, kEventDurationNoWait, true, &event);
664 if(err != noErr)
665 continue;
666 // else
667 if (flags & QEventLoop::ExcludeUserInputEvents) {
668 UInt32 ekind = GetEventKind(event),
669 eclass = GetEventClass(event);
670 switch(eclass) {
671 case kEventClassQt:
672 if(ekind != kEventQtRequestContext)
673 break;
674 // fall through
675 case kEventClassMouse:
676 case kEventClassKeyboard:
677 d->queuedUserInputEvents.append(event);
678 continue;
679 }
680 }
681 }
682
683 if (!filterEvent(&event) && qt_mac_send_event(flags, event, 0))
684 retVal = true;
685 ReleaseEvent(event);
686 } while(!d->interrupt && GetNumEventsInQueue(GetMainEventQueue()) > 0);
687
688#endif
689
690 bool canWait = (d->threadData->canWait
691 && !retVal
692 && !d->interrupt
693 && (flags & QEventLoop::WaitForMoreEvents));
694 if (canWait) {
695 // INVARIANT: We haven't processed any events yet. And we're told
696 // to stay inside this function until at least one event is processed.
697 qt_mac_waitForMoreEvents();
698 flags &= ~QEventLoop::WaitForMoreEvents;
699 } else {
700 // Done with event processing for now.
701 // Leave the function:
702 break;
703 }
704 }
705
706 // If we're interrupted, we need to interrupt the _current_
707 // recursion as well to check if it is still supposed to be
708 // executing. This way we wind down the stack until we land
709 // on a recursion that again calls processEvents (typically
710 // from QEventLoop), and set interrupt to false:
711 if (d->interrupt)
712 interrupt();
713
714#ifdef QT_MAC_USE_COCOA
715 if (interruptLater)
716 QtMacInterruptDispatcherHelp::interruptLater();
717#endif
718
719 return retVal;
720}
721
722void QEventDispatcherMac::wakeUp()
723{
724 Q_D(QEventDispatcherMac);
725 d->serialNumber.ref();
726 CFRunLoopSourceSignal(d->postedEventsSource);
727 CFRunLoopWakeUp(mainRunLoop());
728}
729
730void QEventDispatcherMac::flush()
731{
732 if(qApp) {
733 QWidgetList tlws = QApplication::topLevelWidgets();
734 for(int i = 0; i < tlws.size(); i++) {
735 QWidget *tlw = tlws.at(i);
736 if(tlw->isVisible())
737 macWindowFlush(qt_mac_window_for(tlw));
738 }
739 }
740}
741
742/*****************************************************************************
743 QEventDispatcherMac Implementation
744 *****************************************************************************/
745MacTimerHash QEventDispatcherMacPrivate::macTimerHash;
746bool QEventDispatcherMacPrivate::blockSendPostedEvents = false;
747bool QEventDispatcherMacPrivate::interrupt = false;
748
749#ifdef QT_MAC_USE_COCOA
750QStack<QCocoaModalSessionInfo> QEventDispatcherMacPrivate::cocoaModalSessionStack;
751bool QEventDispatcherMacPrivate::currentExecIsNSAppRun = false;
752bool QEventDispatcherMacPrivate::nsAppRunCalledByQt = false;
753bool QEventDispatcherMacPrivate::cleanupModalSessionsNeeded = false;
754NSModalSession QEventDispatcherMacPrivate::currentModalSessionCached = 0;
755
756void QEventDispatcherMacPrivate::ensureNSAppInitialized()
757{
758 // Some elements in Cocoa require NSApplication to be running before
759 // they get fully initialized, in particular the menu bar. This
760 // function is intended for cases where a dialog is told to execute before
761 // QApplication::exec is called, or the application spins the events loop
762 // manually rather than calling QApplication:exec.
763 // The function makes sure that NSApplication starts running, but stops
764 // it again as soon as the send posted events callback is called. That way
765 // we let Cocoa finish the initialization it seems to need. We'll only
766 // apply this trick at most once for any application, and we avoid doing it
767 // for the common case where main just starts QApplication::exec.
768 if (nsAppRunCalledByQt || [NSApp isRunning])
769 return;
770 nsAppRunCalledByQt = true;
771 QBoolBlocker block1(interrupt, true);
772 QBoolBlocker block2(currentExecIsNSAppRun, true);
773 [NSApp run];
774}
775
776void QEventDispatcherMacPrivate::temporarilyStopAllModalSessions()
777{
778 // Flush, and Stop, all created modal session, and as
779 // such, make them pending again. The next call to
780 // currentModalSession will recreate them again. The
781 // reason to stop all session like this is that otherwise
782 // a call [NSApp stop] would not stop NSApp, but rather
783 // the current modal session. So if we need to stop NSApp
784 // we need to stop all the modal session first. To avoid changing
785 // the stacking order of the windows while doing so, we put
786 // up a block that is used in QCocoaWindow and QCocoaPanel:
787 int stackSize = cocoaModalSessionStack.size();
788 for (int i=0; i<stackSize; ++i) {
789 QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
790 if (info.session) {
791 [NSApp endModalSession:info.session];
792 info.session = 0;
793 }
794 }
795 currentModalSessionCached = 0;
796}
797
798NSModalSession QEventDispatcherMacPrivate::currentModalSession()
799{
800 // If we have one or more modal windows, this function will create
801 // a session for each of those, and return the one for the top.
802 if (currentModalSessionCached)
803 return currentModalSessionCached;
804
805 if (cocoaModalSessionStack.isEmpty())
806 return 0;
807
808 int sessionCount = cocoaModalSessionStack.size();
809 for (int i=0; i<sessionCount; ++i) {
810 QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
811 if (!info.widget)
812 continue;
813 if (info.widget->testAttribute(Qt::WA_DontShowOnScreen))
814 continue;
815 if (!info.session) {
816 QMacCocoaAutoReleasePool pool;
817 NSWindow *window = qt_mac_window_for(info.widget);
818 if (!window)
819 continue;
820
821 ensureNSAppInitialized();
822 QBoolBlocker block1(blockSendPostedEvents, true);
823 info.nswindow = window;
824 [(NSWindow*) info.nswindow retain];
825 int levelBeforeEnterModal = [window level];
826 info.session = [NSApp beginModalSessionForWindow:window];
827 // Make sure we don't stack the window lower that it was before
828 // entering modal, in case it e.g. had the stays-on-top flag set:
829 if (levelBeforeEnterModal > [window level])
830 [window setLevel:levelBeforeEnterModal];
831 }
832 currentModalSessionCached = info.session;
833 cleanupModalSessionsNeeded = false;
834 }
835 return currentModalSessionCached;
836}
837
838static void setChildrenWorksWhenModal(QWidget *widget, bool worksWhenModal)
839{
840 // For NSPanels (but not NSWindows, sadly), we can set the flag
841 // worksWhenModal, so that they are active even when they are not modal.
842 QList<QDialog *> dialogs = widget->findChildren<QDialog *>();
843 for (int i=0; i<dialogs.size(); ++i){
844 NSWindow *window = qt_mac_window_for(dialogs[i]);
845 if (window && [window isKindOfClass:[NSPanel class]]) {
846 [static_cast<NSPanel *>(window) setWorksWhenModal:worksWhenModal];
847 if (worksWhenModal && [window isVisible]){
848 [window orderFront:window];
849 }
850 }
851 }
852}
853
854void QEventDispatcherMacPrivate::updateChildrenWorksWhenModal()
855{
856 // Make the dialog children of the widget
857 // active. And make the dialog children of
858 // the previous modal dialog unactive again:
859 QMacCocoaAutoReleasePool pool;
860 int size = cocoaModalSessionStack.size();
861 if (size > 0){
862 if (QWidget *prevModal = cocoaModalSessionStack[size-1].widget)
863 setChildrenWorksWhenModal(prevModal, true);
864 if (size > 1){
865 if (QWidget *prevModal = cocoaModalSessionStack[size-2].widget)
866 setChildrenWorksWhenModal(prevModal, false);
867 }
868 }
869}
870
871void QEventDispatcherMacPrivate::cleanupModalSessions()
872{
873 // Go through the list of modal sessions, and end those
874 // that no longer has a widget assosiated; no widget means
875 // the the session has logically ended. The reason we wait like
876 // this to actually end the sessions for real (rather than at the
877 // point they were marked as stopped), is that ending a session
878 // when no other session runs below it on the stack will make cocoa
879 // drop some events on the floor.
880 QMacCocoaAutoReleasePool pool;
881 int stackSize = cocoaModalSessionStack.size();
882
883 for (int i=stackSize-1; i>=0; --i) {
884 QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
885 if (info.widget) {
886 // This session has a widget, and is therefore not marked
887 // as stopped. So just make it current. There might still be other
888 // stopped sessions on the stack, but those will be stopped on
889 // a later "cleanup" call.
890 currentModalSessionCached = info.session;
891 break;
892 }
893 cocoaModalSessionStack.remove(i);
894 currentModalSessionCached = 0;
895 if (info.session) {
896 [NSApp endModalSession:info.session];
897 [(NSWindow *)info.nswindow release];
898 }
899 }
900
901 updateChildrenWorksWhenModal();
902 cleanupModalSessionsNeeded = false;
903}
904
905void QEventDispatcherMacPrivate::beginModalSession(QWidget *widget)
906{
907 // Add a new, empty (null), NSModalSession to the stack.
908 // It will become active the next time QEventDispatcher::processEvents is called.
909 // A QCocoaModalSessionInfo is considered pending to become active if the widget pointer
910 // is non-zero, and the session pointer is zero (it will become active upon a call to
911 // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if
912 // the widget pointer is zero, and the session pointer is non-zero (it will be fully
913 // stopped in cleanupModalSessions()).
914 QCocoaModalSessionInfo info = {widget, 0, 0};
915 cocoaModalSessionStack.push(info);
916 updateChildrenWorksWhenModal();
917 currentModalSessionCached = 0;
918}
919
920void QEventDispatcherMacPrivate::endModalSession(QWidget *widget)
921{
922 // Mark all sessions attached to widget as pending to be stopped. We do this
923 // by setting the widget pointer to zero, but leave the session pointer.
924 // We don't tell cocoa to stop any sessions just yet, because cocoa only understands
925 // when we stop the _current_ modal session (which is the session on top of
926 // the stack, and might not belong to 'widget').
927 int stackSize = cocoaModalSessionStack.size();
928 for (int i=stackSize-1; i>=0; --i) {
929 QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
930 if (info.widget == widget) {
931 info.widget = 0;
932 if (i == stackSize-1) {
933 // The top sessions ended. Interrupt the event dispatcher
934 // to start spinning the correct session immidiatly:
935 currentModalSessionCached = 0;
936 cleanupModalSessionsNeeded = true;
937 QEventDispatcherMac::instance()->interrupt();
938 }
939 }
940 }
941}
942
943#endif
944
945QEventDispatcherMacPrivate::QEventDispatcherMacPrivate()
946{
947}
948
949QEventDispatcherMac::QEventDispatcherMac(QObject *parent)
950 : QAbstractEventDispatcher(*new QEventDispatcherMacPrivate, parent)
951{
952 Q_D(QEventDispatcherMac);
953 CFRunLoopSourceContext context;
954 bzero(&context, sizeof(CFRunLoopSourceContext));
955 context.info = d;
956 context.equal = QEventDispatcherMacPrivate::postedEventSourceEqualCallback;
957 context.perform = QEventDispatcherMacPrivate::postedEventsSourcePerformCallback;
958 d->postedEventsSource = CFRunLoopSourceCreate(0, 0, &context);
959 Q_ASSERT(d->postedEventsSource);
960 CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
961
962 CFRunLoopObserverContext observerContext;
963 bzero(&observerContext, sizeof(CFRunLoopObserverContext));
964 observerContext.info = this;
965 d->waitingObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
966 kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting,
967 true, 0,
968 QEventDispatcherMacPrivate::waitingObserverCallback,
969 &observerContext);
970 CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes);
971
972 /* The first cycle in the loop adds the source and the events of the source
973 are not processed.
974 We use an observer to process the posted events for the first
975 execution of the loop. */
976 CFRunLoopObserverContext firstTimeObserverContext;
977 bzero(&firstTimeObserverContext, sizeof(CFRunLoopObserverContext));
978 firstTimeObserverContext.info = d;
979 d->firstTimeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
980 kCFRunLoopEntry,
981 /* repeats = */ false,
982 0,
983 QEventDispatcherMacPrivate::firstLoopEntry,
984 &firstTimeObserverContext);
985 CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, kCFRunLoopCommonModes);
986}
987
988void QEventDispatcherMacPrivate::waitingObserverCallback(CFRunLoopObserverRef,
989 CFRunLoopActivity activity, void *info)
990{
991 if (activity == kCFRunLoopBeforeWaiting)
992 emit static_cast<QEventDispatcherMac*>(info)->aboutToBlock();
993 else
994 emit static_cast<QEventDispatcherMac*>(info)->awake();
995}
996
997Boolean QEventDispatcherMacPrivate::postedEventSourceEqualCallback(const void *info1, const void *info2)
998{
999 return info1 == info2;
1000}
1001
1002inline static void processPostedEvents(QEventDispatcherMacPrivate *const d, const bool blockSendPostedEvents)
1003{
1004 if (blockSendPostedEvents) {
1005 // We're told to not send posted events (because the event dispatcher
1006 // is currently working on setting up the correct session to run). But
1007 // we still need to make sure that we don't fall asleep until pending events
1008 // are sendt, so we just signal this need, and return:
1009 CFRunLoopSourceSignal(d->postedEventsSource);
1010 return;
1011 }
1012
1013#ifdef QT_MAC_USE_COCOA
1014 if (d->cleanupModalSessionsNeeded)
1015 d->cleanupModalSessions();
1016#endif
1017
1018 if (d->interrupt) {
1019#ifdef QT_MAC_USE_COCOA
1020 if (d->currentExecIsNSAppRun) {
1021 // The event dispatcher has been interrupted. But since
1022 // [NSApplication run] is running the event loop, we
1023 // delayed stopping it until now (to let cocoa process
1024 // pending cocoa events first).
1025 if (d->currentModalSessionCached)
1026 d->temporarilyStopAllModalSessions();
1027 [NSApp stop:NSApp];
1028 d->cancelWaitForMoreEvents();
1029 }
1030#endif
1031 return;
1032 }
1033
1034 if (!d->threadData->canWait || (d->serialNumber != d->lastSerial)) {
1035 d->lastSerial = d->serialNumber;
1036 QApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
1037 }
1038}
1039
1040void QEventDispatcherMacPrivate::firstLoopEntry(CFRunLoopObserverRef ref,
1041 CFRunLoopActivity activity,
1042 void *info)
1043{
1044 Q_UNUSED(ref);
1045 Q_UNUSED(activity);
1046#ifdef QT_MAC_USE_COCOA
1047 QApplicationPrivate::qt_initAfterNSAppStarted();
1048#endif
1049 processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents);
1050}
1051
1052void QEventDispatcherMacPrivate::postedEventsSourcePerformCallback(void *info)
1053{
1054 processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents);
1055}
1056
1057#ifdef QT_MAC_USE_COCOA
1058void QEventDispatcherMacPrivate::cancelWaitForMoreEvents()
1059{
1060 // In case the event dispatcher is waiting for more
1061 // events somewhere, we post a dummy event to wake it up:
1062 QMacCocoaAutoReleasePool pool;
1063 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint
1064 modifierFlags:0 timestamp:0. windowNumber:0 context:0
1065 subtype:QtCocoaEventSubTypeWakeup data1:0 data2:0] atStart:NO];
1066}
1067#endif
1068
1069void QEventDispatcherMac::interrupt()
1070{
1071 Q_D(QEventDispatcherMac);
1072 d->interrupt = true;
1073 wakeUp();
1074
1075#ifndef QT_MAC_USE_COCOA
1076 CFRunLoopStop(mainRunLoop());
1077#else
1078 // We do nothing more here than setting d->interrupt = true, and
1079 // poke the event loop if it is sleeping. Actually stopping
1080 // NSApp, or the current modal session, is done inside the send
1081 // posted events callback. We do this to ensure that all current pending
1082 // cocoa events gets delivered before we stop. Otherwise, if we now stop
1083 // the last event loop recursion, cocoa will just drop pending posted
1084 // events on the floor before we get a chance to reestablish a new session.
1085 d->cancelWaitForMoreEvents();
1086#endif
1087}
1088
1089QEventDispatcherMac::~QEventDispatcherMac()
1090{
1091 Q_D(QEventDispatcherMac);
1092 //timer cleanup
1093 MacTimerHash::iterator it = QEventDispatcherMacPrivate::macTimerHash.begin();
1094 while (it != QEventDispatcherMacPrivate::macTimerHash.end()) {
1095 MacTimerInfo *t = it.value();
1096 if (t->runLoopTimer) {
1097 CFRunLoopTimerInvalidate(t->runLoopTimer);
1098 CFRelease(t->runLoopTimer);
1099 }
1100 delete t;
1101 ++it;
1102 }
1103 QEventDispatcherMacPrivate::macTimerHash.clear();
1104
1105 // Remove CFSockets from the runloop.
1106 for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) {
1107 MacSocketInfo *socketInfo = (*it);
1108 if (CFSocketIsValid(socketInfo->socket)) {
1109 qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop);
1110 CFRunLoopSourceInvalidate(socketInfo->runloop);
1111 CFRelease(socketInfo->runloop);
1112 CFSocketInvalidate(socketInfo->socket);
1113 CFRelease(socketInfo->socket);
1114 }
1115 }
1116 CFRunLoopRemoveSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
1117 CFRelease(d->postedEventsSource);
1118
1119 CFRunLoopObserverInvalidate(d->waitingObserver);
1120 CFRelease(d->waitingObserver);
1121
1122 CFRunLoopObserverInvalidate(d->firstTimeObserver);
1123 CFRelease(d->firstTimeObserver);
1124}
1125
1126#ifdef QT_MAC_USE_COCOA
1127
1128QtMacInterruptDispatcherHelp* QtMacInterruptDispatcherHelp::instance = 0;
1129
1130QtMacInterruptDispatcherHelp::QtMacInterruptDispatcherHelp() : cancelled(false)
1131{
1132 // The whole point of this class is that we enable a way to interrupt
1133 // the event dispatcher when returning back to a lower recursion level
1134 // than where interruptLater was called. This is needed to detect if
1135 // [NSApp run] should still be running at the recursion level it is at.
1136 // Since the interrupt is canceled if processEvents is called before
1137 // this object gets deleted, we also avoid interrupting unnecessary.
1138 deleteLater();
1139}
1140
1141QtMacInterruptDispatcherHelp::~QtMacInterruptDispatcherHelp()
1142{
1143 if (cancelled)
1144 return;
1145 instance = 0;
1146 QEventDispatcherMac::instance()->interrupt();
1147}
1148
1149void QtMacInterruptDispatcherHelp::cancelInterruptLater()
1150{
1151 if (!instance)
1152 return;
1153 instance->cancelled = true;
1154 delete instance;
1155 instance = 0;
1156}
1157
1158void QtMacInterruptDispatcherHelp::interruptLater()
1159{
1160 cancelInterruptLater();
1161 instance = new QtMacInterruptDispatcherHelp;
1162}
1163
1164#endif
1165
1166QT_END_NAMESPACE
1167
Note: See TracBrowser for help on using the repository browser.