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

Last change on this file since 561 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

File size: 41.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
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 [email protected].
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
94QT_BEGIN_NAMESPACE
95
96QT_USE_NAMESPACE
97#endif
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 result = true;
496 break;
497
498 default:
499 break;
500 }
501 return result;
502}
503#endif
504
505static inline void qt_mac_waitForMoreEvents()
506{
507#ifndef QT_MAC_USE_COCOA
508 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true) == kCFRunLoopRunTimedOut) ;
509#else
510 // If no event exist in the cocoa event que, wait
511 // (and free up cpu time) until at least one event occur.
512 // This implementation is a bit on the edge, but seems to
513 // work fine:
514 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
515 untilDate:[NSDate distantFuture]
516 inMode:NSDefaultRunLoopMode
517 dequeue:YES];
518 if (event)
519 [NSApp postEvent:event atStart:YES];
520#endif
521}
522
523#ifdef QT_MAC_USE_COCOA
524static inline void qt_mac_waitForMoreModalSessionEvents()
525{
526 // If no event exist in the cocoa event que, wait
527 // (and free up cpu time) until at least one event occur.
528 // This implementation is a bit on the edge, but seems to
529 // work fine:
530 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
531 untilDate:[NSDate distantFuture]
532 inMode:NSModalPanelRunLoopMode
533 dequeue:YES];
534 if (event)
535 [NSApp postEvent:event atStart:YES];
536}
537#endif
538
539bool QEventDispatcherMac::processEvents(QEventLoop::ProcessEventsFlags flags)
540{
541 Q_D(QEventDispatcherMac);
542 d->interrupt = false;
543 // In case we end up recursing while we now process events, make sure
544 // that we send remaining posted Qt events before this call returns:
545 wakeUp();
546 emit awake();
547
548 bool retVal = false;
549 forever {
550 if (d->interrupt)
551 break;
552
553#ifdef QT_MAC_USE_COCOA
554 QMacCocoaAutoReleasePool pool;
555 NSEvent* event = 0;
556
557 // If Qt is used as a plugin, or just added into a native cocoa
558 // application, we should not run or stop NSApplication;
559 // This will be done from outside Qt.
560 // And if processEvents is called manually (rather than from QEventLoop), we
561 // cannot enter a tight loop and block the call, but instead return after one flush:
562 bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning];
563 bool canExec_Qt = flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec;
564
565 if (canExec_Qt && canExec_3rdParty) {
566 // We can use exec-mode, meaning that we can stay in a tight loop until
567 // interrupted. This is mostly an optimization, but it also allow us
568 // to use [NSApp run], which is the recommended way of running applications
569 // in cocoa. [NSApp run] should be called at least once for any cocoa app.
570 if (NSModalSession session = d->currentModalSession()) {
571 QBoolBlocker execGuard(d->currentExecIsNSAppRun, false);
572 while (!d->interrupt && [NSApp runModalSession:session] == NSRunContinuesResponse)
573 qt_mac_waitForMoreModalSessionEvents();
574 if (!d->interrupt && session == d->currentModalSessionCached) {
575 // INVARIANT: Someone called e.g. [NSApp stopModal:] from outside the event
576 // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
577 // 'session' as well. As a result, we need to restart all internal sessions:
578 d->temporarilyStopAllModalSessions();
579 }
580 } else {
581 d->nsAppRunCalledByQt = true;
582 QBoolBlocker execGuard(d->currentExecIsNSAppRun, true);
583 [NSApp run];
584 }
585 retVal = true;
586 } else do {
587 // INVARIANT: We cannot block the thread (and run in a tight loop).
588 // Instead we will process all current pending events and return.
589 bool mustRelease = false;
590
591 if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
592 // Process a pending user input event
593 mustRelease = true;
594 event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst());
595 } else {
596 if (NSModalSession session = d->currentModalSession()) {
597 if (flags & QEventLoop::WaitForMoreEvents)
598 qt_mac_waitForMoreModalSessionEvents();
599 NSInteger status = [NSApp runModalSession:session];
600 if (status != NSRunContinuesResponse && session == d->currentModalSessionCached) {
601 // INVARIANT: Someone called e.g. [NSApp stopModal:] from outside the event
602 // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
603 // 'session' as well. As a result, we need to restart all internal sessions:
604 d->temporarilyStopAllModalSessions();
605 }
606 retVal = true;
607 break;
608 } else {
609 event = [NSApp nextEventMatchingMask:NSAnyEventMask
610 untilDate:nil
611 inMode:NSDefaultRunLoopMode
612 dequeue: YES];
613
614 if (event != nil) {
615 if (flags & QEventLoop::ExcludeUserInputEvents) {
616 if (IsMouseOrKeyEvent(event)) {
617 // retain event here?
618 [event retain];
619 d->queuedUserInputEvents.append(event);
620 continue;
621 }
622 }
623 }
624 }
625 }
626 if (event) {
627 if (!filterEvent(event) && qt_mac_send_event(flags, event, 0))
628 retVal = true;
629 if (mustRelease)
630 [event release];
631 }
632 } while(!d->interrupt && event != nil);
633
634#else
635 do {
636 EventRef event;
637 if (!(flags & QEventLoop::ExcludeUserInputEvents)
638 && !d->queuedUserInputEvents.isEmpty()) {
639 // process a pending user input event
640 event = static_cast<EventRef>(d->queuedUserInputEvents.takeFirst());
641 } else {
642 OSStatus err = ReceiveNextEvent(0,0, kEventDurationNoWait, true, &event);
643 if(err != noErr)
644 continue;
645 // else
646 if (flags & QEventLoop::ExcludeUserInputEvents) {
647 UInt32 ekind = GetEventKind(event),
648 eclass = GetEventClass(event);
649 switch(eclass) {
650 case kEventClassQt:
651 if(ekind != kEventQtRequestContext)
652 break;
653 // fall through
654 case kEventClassMouse:
655 case kEventClassKeyboard:
656 d->queuedUserInputEvents.append(event);
657 continue;
658 }
659 }
660 }
661
662 if (!filterEvent(&event) && qt_mac_send_event(flags, event, 0))
663 retVal = true;
664 ReleaseEvent(event);
665 } while(!d->interrupt && GetNumEventsInQueue(GetMainEventQueue()) > 0);
666
667#endif
668
669 bool canWait = (d->threadData->canWait
670 && !retVal
671 && !d->interrupt
672 && (flags & QEventLoop::WaitForMoreEvents));
673 if (canWait) {
674 // INVARIANT: We haven't processed any events yet. And we're told
675 // to stay inside this function until at least one event is processed.
676 qt_mac_waitForMoreEvents();
677 flags &= ~QEventLoop::WaitForMoreEvents;
678 } else {
679 // Done with event processing for now.
680 // Leave the function:
681 break;
682 }
683 }
684
685#ifdef QT_MAC_USE_COCOA
686 // In case we _now_ process events using [NSApp run], we need to stop it to
687 // ensure that:
688 // 1. the QEventLoop that called us is still executing, or
689 // 2. we have a modal session that needs to be spun instead.
690 // In case this is a plain call to processEvents (perhaps from a loop)
691 // from the application (rather than from a QEventLoop), we delay the
692 // interrupting until we/ actually enter a lower loop level (hence the
693 // deffered delete of the object below):
694 QtMacInterruptDispatcherHelp::interruptLater();
695#endif
696
697 if (d->interrupt) {
698 // We should continue to leave all recursion to processEvents until
699 // processEvents is called again (e.g. from a QEventLoop that
700 // was not yet told to quit:
701 interrupt();
702 }
703
704 return retVal;
705}
706
707void QEventDispatcherMac::wakeUp()
708{
709 Q_D(QEventDispatcherMac);
710 d->serialNumber.ref();
711 CFRunLoopSourceSignal(d->postedEventsSource);
712 CFRunLoopWakeUp(mainRunLoop());
713}
714
715void QEventDispatcherMac::flush()
716{
717 if(qApp) {
718 QWidgetList tlws = QApplication::topLevelWidgets();
719 for(int i = 0; i < tlws.size(); i++) {
720 QWidget *tlw = tlws.at(i);
721 if(tlw->isVisible())
722 macWindowFlush(qt_mac_window_for(tlw));
723 }
724 }
725}
726
727/*****************************************************************************
728 QEventDispatcherMac Implementation
729 *****************************************************************************/
730MacTimerHash QEventDispatcherMacPrivate::macTimerHash;
731bool QEventDispatcherMacPrivate::blockSendPostedEvents = false;
732
733#ifdef QT_MAC_USE_COCOA
734QStack<QCocoaModalSessionInfo> QEventDispatcherMacPrivate::cocoaModalSessionStack;
735bool QEventDispatcherMacPrivate::currentExecIsNSAppRun = false;
736bool QEventDispatcherMacPrivate::nsAppRunCalledByQt = false;
737NSModalSession QEventDispatcherMacPrivate::currentModalSessionCached = 0;
738
739int QEventDispatcherMacPrivate::activeModalSessionCount()
740{
741 // Returns the number of modal sessions created
742 // (and not just pushed onto the stack, pending to be created)
743 int count = 0;
744 for (int i=cocoaModalSessionStack.size()-1; i>=0; --i) {
745 QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
746 if (info.session)
747 ++count;
748 }
749 return count;
750}
751
752void QEventDispatcherMacPrivate::temporarilyStopAllModalSessions()
753{
754 // Stop all created modal session, and as such, make then
755 // pending again. The next call to currentModalSession will
756 // recreate the session on top again:
757 int stackSize = cocoaModalSessionStack.size();
758 for (int i=stackSize-1; i>=0; --i) {
759 QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
760 if (info.session) {
761 [NSApp endModalSession:info.session];
762 info.session = 0;
763 }
764 }
765 currentModalSessionCached = 0;
766}
767
768NSModalSession QEventDispatcherMacPrivate::currentModalSession()
769{
770 // If we have one or more modal windows, this function will create
771 // a session for each of those, and return the one for the top.
772 if (currentModalSessionCached)
773 return currentModalSessionCached;
774
775 if (cocoaModalSessionStack.isEmpty())
776 return 0;
777
778 // Since this code will end up calling our Qt event handler
779 // (also from beginModalSessionForWindow), we need to block
780 // that to avoid side effects of events beeing delivered:
781 QBoolBlocker block(blockSendPostedEvents, true);
782
783 if (![NSApp isRunning]) {
784 // Sadly, we need to introduce this little event flush
785 // to stop dialogs from blinking/poping in front if a
786 // modal session restart was needed:
787 while (NSEvent *event = [NSApp nextEventMatchingMask:0
788 untilDate:nil
789 inMode:NSDefaultRunLoopMode
790 dequeue: YES]) {
791 qt_mac_send_event(0, event, 0);
792 }
793 }
794
795 int sessionCount = cocoaModalSessionStack.size();
796 for (int i=0; i<sessionCount; ++i) {
797 QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
798 if (!info.widget)
799 continue;
800 if (info.widget->testAttribute(Qt::WA_DontShowOnScreen))
801 continue;
802 if (!info.session) {
803 QMacCocoaAutoReleasePool pool;
804 NSWindow *window = qt_mac_window_for(info.widget);
805 if (!window)
806 continue;
807 info.session = [NSApp beginModalSessionForWindow:window];
808 }
809 currentModalSessionCached = info.session;
810 }
811
812 return currentModalSessionCached;
813}
814
815static void setChildrenWorksWhenModal(QWidget *widget, bool worksWhenModal)
816{
817 // For NSPanels (but not NSWindows, sadly), we can set the flag
818 // worksWhenModal, so that they are active even when they are not modal.
819 QList<QDialog *> dialogs = widget->findChildren<QDialog *>();
820 for (int i=0; i<dialogs.size(); ++i){
821 NSWindow *window = qt_mac_window_for(dialogs[i]);
822 if (window && [window isKindOfClass:[NSPanel class]]) {
823 [static_cast<NSPanel *>(window) setWorksWhenModal:worksWhenModal];
824 if (worksWhenModal && dialogs[i]->isVisible()){
825 [window orderFront:window];
826 }
827 }
828 }
829}
830
831void QEventDispatcherMacPrivate::updateChildrenWorksWhenModal()
832{
833 // Make the dialog children of the widget
834 // active. And make the dialog children of
835 // the previous modal dialog unactive again:
836 int size = cocoaModalSessionStack.size();
837 if (size > 0){
838 if (QWidget *prevModal = cocoaModalSessionStack[size-1].widget)
839 setChildrenWorksWhenModal(prevModal, true);
840 if (size > 1){
841 if (QWidget *prevModal = cocoaModalSessionStack[size-2].widget)
842 setChildrenWorksWhenModal(prevModal, false);
843 }
844 }
845}
846
847void QEventDispatcherMacPrivate::beginModalSession(QWidget *widget)
848{
849 // Add a new, empty (null), NSModalSession to the stack.
850 // It will become active the next time QEventDispatcher::processEvents is called.
851 // A QCocoaModalSessionInfo is considered pending to become active if the widget pointer
852 // is non-zero, and the session pointer is zero (it will become active upon a call to
853 // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if
854 // the widget pointer is zero, and the session pointer is non-zero (it will be fully
855 // stopped in endModalSession().
856 QCocoaModalSessionInfo info = {widget, 0};
857 cocoaModalSessionStack.push(info);
858 updateChildrenWorksWhenModal();
859 currentModalSessionCached = 0;
860}
861
862void QEventDispatcherMacPrivate::endModalSession(QWidget *widget)
863{
864 // Mark all sessions attached to widget as pending to be stopped. We do this
865 // by setting the widget pointer to zero, but leave the session pointer.
866 // We don't tell cocoa to stop any sessions just yet, because cocoa only understands
867 // when we stop the _current_ modal session (which is the session on top of
868 // the stack, and might not belong to 'widget').
869 int stackSize = cocoaModalSessionStack.size();
870 for (int i=stackSize-1; i>=0; --i) {
871 QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
872 if (info.widget == widget)
873 info.widget = 0;
874 }
875
876 // Now we stop, and remove, all sessions marked as pending
877 // to be stopped on _top_ of the stack, if any:
878 bool needToInterruptEventDispatcher = false;
879 bool needToUpdateChildrenWorksWhenModal = false;
880
881 for (int i=stackSize-1; i>=0; --i) {
882 QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
883 if (info.widget)
884 break;
885 cocoaModalSessionStack.remove(i);
886 needToUpdateChildrenWorksWhenModal = true;
887 currentModalSessionCached = 0;
888 if (info.session) {
889 [NSApp endModalSession:info.session];
890 needToInterruptEventDispatcher = true;
891 }
892 }
893
894 if (needToUpdateChildrenWorksWhenModal)
895 updateChildrenWorksWhenModal();
896 if (needToInterruptEventDispatcher)
897 QEventDispatcherMac::instance()->interrupt();
898}
899
900#endif
901
902QEventDispatcherMacPrivate::QEventDispatcherMacPrivate()
903 : interrupt(false)
904{
905}
906
907QEventDispatcherMac::QEventDispatcherMac(QObject *parent)
908 : QAbstractEventDispatcher(*new QEventDispatcherMacPrivate, parent)
909{
910 Q_D(QEventDispatcherMac);
911 CFRunLoopSourceContext context;
912 bzero(&context, sizeof(CFRunLoopSourceContext));
913 context.info = d;
914 context.equal = QEventDispatcherMacPrivate::postedEventSourceEqualCallback;
915 context.perform = QEventDispatcherMacPrivate::postedEventsSourcePerformCallback;
916 d->postedEventsSource = CFRunLoopSourceCreate(0, 0, &context);
917 Q_ASSERT(d->postedEventsSource);
918 CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
919
920 CFRunLoopObserverContext observerContext;
921 bzero(&observerContext, sizeof(CFRunLoopObserverContext));
922 observerContext.info = this;
923 d->waitingObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
924 kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting,
925 true, 0,
926 QEventDispatcherMacPrivate::waitingObserverCallback,
927 &observerContext);
928 CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes);
929
930 /* The first cycle in the loop adds the source and the events of the source
931 are not processed.
932 We use an observer to process the posted events for the first
933 execution of the loop. */
934 CFRunLoopObserverContext firstTimeObserverContext;
935 bzero(&firstTimeObserverContext, sizeof(CFRunLoopObserverContext));
936 firstTimeObserverContext.info = d;
937 d->firstTimeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
938 kCFRunLoopEntry,
939 /* repeats = */ false,
940 0,
941 QEventDispatcherMacPrivate::firstLoopEntry,
942 &firstTimeObserverContext);
943 CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, kCFRunLoopCommonModes);
944}
945
946void QEventDispatcherMacPrivate::waitingObserverCallback(CFRunLoopObserverRef,
947 CFRunLoopActivity activity, void *info)
948{
949 if (activity == kCFRunLoopBeforeWaiting)
950 emit static_cast<QEventDispatcherMac*>(info)->aboutToBlock();
951 else
952 emit static_cast<QEventDispatcherMac*>(info)->awake();
953}
954
955Boolean QEventDispatcherMacPrivate::postedEventSourceEqualCallback(const void *info1, const void *info2)
956{
957 return info1 == info2;
958}
959
960inline static void processPostedEvents(QEventDispatcherMacPrivate *const d, const bool blockSendPostedEvents)
961{
962 if (blockSendPostedEvents || d->interrupt) {
963 CFRunLoopSourceSignal(d->postedEventsSource);
964 } else {
965 if (!d->threadData->canWait || (d->serialNumber != d->lastSerial)) {
966 d->lastSerial = d->serialNumber;
967 QApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
968 }
969 }
970}
971
972void QEventDispatcherMacPrivate::firstLoopEntry(CFRunLoopObserverRef ref,
973 CFRunLoopActivity activity,
974 void *info)
975{
976 Q_UNUSED(ref);
977 Q_UNUSED(activity);
978 processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents);
979}
980
981void QEventDispatcherMacPrivate::postedEventsSourcePerformCallback(void *info)
982{
983 processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents);
984}
985
986void QEventDispatcherMac::interrupt()
987{
988 Q_D(QEventDispatcherMac);
989 d->interrupt = true;
990 wakeUp();
991
992#ifndef QT_MAC_USE_COCOA
993 CFRunLoopStop(mainRunLoop());
994#else
995 QMacCocoaAutoReleasePool pool;
996 // In case we wait for more events inside
997 // processEvents (or NSApp run), post a dummy to wake it up:
998 static const short NSAppShouldStopForQt = SHRT_MAX;
999 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint
1000 modifierFlags:0 timestamp:0. windowNumber:0 context:0
1001 subtype:NSAppShouldStopForQt data1:0 data2:0] atStart:NO];
1002
1003 if (d->activeModalSessionCount() == 0) {
1004 // We should only stop NSApp if we actually started it (and
1005 // not some 3rd party application, e.g. if we are a plugin).
1006 if (d->nsAppRunCalledByQt)
1007 [NSApp stop:NSApp];
1008 }
1009#endif
1010}
1011
1012QEventDispatcherMac::~QEventDispatcherMac()
1013{
1014 Q_D(QEventDispatcherMac);
1015 //timer cleanup
1016 MacTimerHash::iterator it = QEventDispatcherMacPrivate::macTimerHash.begin();
1017 while (it != QEventDispatcherMacPrivate::macTimerHash.end()) {
1018 MacTimerInfo *t = it.value();
1019 if (t->runLoopTimer) {
1020 CFRunLoopTimerInvalidate(t->runLoopTimer);
1021 CFRelease(t->runLoopTimer);
1022 }
1023 delete t;
1024 ++it;
1025 }
1026 QEventDispatcherMacPrivate::macTimerHash.clear();
1027
1028 // Remove CFSockets from the runloop.
1029 for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) {
1030 MacSocketInfo *socketInfo = (*it);
1031 if (CFSocketIsValid(socketInfo->socket)) {
1032 qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop);
1033 CFRunLoopSourceInvalidate(socketInfo->runloop);
1034 CFRelease(socketInfo->runloop);
1035 CFSocketInvalidate(socketInfo->socket);
1036 CFRelease(socketInfo->socket);
1037 }
1038 }
1039 CFRunLoopRemoveSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
1040 CFRelease(d->postedEventsSource);
1041
1042 CFRunLoopObserverInvalidate(d->waitingObserver);
1043 CFRelease(d->waitingObserver);
1044
1045 CFRunLoopObserverInvalidate(d->firstTimeObserver);
1046 CFRelease(d->firstTimeObserver);
1047}
1048
1049/////////////////////////////////////////////////////////////////////////////
1050
1051#ifdef QT_MAC_USE_COCOA
1052
1053QtMacInterruptDispatcherHelp* QtMacInterruptDispatcherHelp::instance = 0;
1054
1055QtMacInterruptDispatcherHelp::QtMacInterruptDispatcherHelp() : cancelled(false)
1056{
1057 // This is the whole point of encapsulation this code
1058 // inside a class; we can make the code (inside destructor)
1059 // execute on lower loop level:
1060 deleteLater();
1061}
1062
1063QtMacInterruptDispatcherHelp::~QtMacInterruptDispatcherHelp()
1064{
1065 if (cancelled)
1066 return;
1067
1068 instance = 0;
1069
1070 if (QEventDispatcherMacPrivate::currentExecIsNSAppRun) {
1071 int activeCount = QEventDispatcherMacPrivate::activeModalSessionCount();
1072 if (activeCount > 0) {
1073 // The problem we now have hit: [NSApp stop] will not stop NSApp
1074 // if a session is active; it will stop the session instead.
1075 // So to stop NSApp, we need to temporarily stop all the
1076 // sessions, then stop NSApp, then restart the session on top again.
1077 // We need to do this to ensure that we're not stuck inside
1078 // [NSApp run] when we really should be running a modal session:
1079 QEventDispatcherMacPrivate::temporarilyStopAllModalSessions();
1080 }
1081 }
1082 // Always interrupt once more in case the modal session stack changed
1083 // while processEvents was called manually from within the application:
1084 QEventDispatcherMac::instance()->interrupt();
1085}
1086
1087void QtMacInterruptDispatcherHelp::interruptLater() {
1088 if (instance) {
1089 instance->cancelled = true;
1090 delete instance;
1091 }
1092 instance = new QtMacInterruptDispatcherHelp;
1093}
1094
1095#endif
1096
1097QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.