1/*
2 * tkMacOSXNotify.c --
3 *
4 *	This file contains the implementation of a tcl event source
5 *	for the AppKit event loop.
6 *
7 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
8 * Copyright 2001-2009, Apple Inc.
9 * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net>
10 *
11 * See the file "license.terms" for information on usage and redistribution
12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13 *
14 * RCS: @(#) $Id$
15 */
16
17#include "tkMacOSXPrivate.h"
18#include "tkMacOSXEvent.h"
19#include <tclInt.h>
20#include <pthread.h>
21#import <objc/objc-auto.h>
22
23typedef struct ThreadSpecificData {
24    int initialized, sendEventNestingLevel;
25    NSEvent *currentEvent;
26} ThreadSpecificData;
27static Tcl_ThreadDataKey dataKey;
28
29#define TSD_INIT() ThreadSpecificData *tsdPtr = Tcl_GetThreadData(&dataKey, \
30	    sizeof(ThreadSpecificData))
31
32static void TkMacOSXNotifyExitHandler(ClientData clientData);
33static void TkMacOSXEventsSetupProc(ClientData clientData, int flags);
34static void TkMacOSXEventsCheckProc(ClientData clientData, int flags);
35
36#pragma mark TKApplication(TKNotify)
37
38@interface NSApplication(TKNotify)
39- (void)_modalSession:(NSModalSession)session sendEvent:(NSEvent *)event;
40@end
41
42@implementation NSWindow(TKNotify)
43- (id)tkDisplayIfNeeded {
44    if (![self isAutodisplay]) {
45	[self displayIfNeeded];
46    }
47    return nil;
48}
49@end
50
51@implementation TKApplication(TKNotify)
52- (NSEvent *)nextEventMatchingMask:(NSUInteger)mask
53	untilDate:(NSDate *)expiration inMode:(NSString *)mode
54	dequeue:(BOOL)deqFlag {
55    NSAutoreleasePool *pool = [NSAutoreleasePool new];
56    [NSApp makeWindowsPerform:@selector(tkDisplayIfNeeded) inOrder:NO];
57    int oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL);
58    NSEvent *event = [[super nextEventMatchingMask:mask untilDate:expiration
59	    inMode:mode dequeue:deqFlag] retain];
60    Tcl_SetServiceMode(oldMode);
61    if (event) {
62	TSD_INIT();
63	if (tsdPtr->sendEventNestingLevel) {
64	    if (![NSApp tkProcessEvent:event]) {
65		[event release];
66		event = nil;
67	    }
68	}
69    }
70    [pool drain];
71    return [event autorelease];
72}
73- (void)sendEvent:(NSEvent *)theEvent {
74    TSD_INIT();
75    int oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL);
76    tsdPtr->sendEventNestingLevel++;
77    [super sendEvent:theEvent];
78    tsdPtr->sendEventNestingLevel--;
79    Tcl_SetServiceMode(oldMode);
80    [NSApp tkCheckPasteboard];
81}
82@end
83
84#pragma mark -
85
86/*
87 *----------------------------------------------------------------------
88 *
89 * GetRunLoopMode --
90 *
91 * Results:
92 *	RunLoop mode that should be passed to -nextEventMatchingMask:
93 *
94 * Side effects:
95 *	None.
96 *
97 *----------------------------------------------------------------------
98 */
99
100static NSString *
101GetRunLoopMode(NSModalSession modalSession)
102{
103    NSString *runLoopMode = nil;
104
105    if (modalSession) {
106	runLoopMode = NSModalPanelRunLoopMode;
107    } else if (TkMacOSXGetCapture()) {
108	runLoopMode = NSEventTrackingRunLoopMode;
109    }
110    if (!runLoopMode) {
111	runLoopMode = [[NSRunLoop currentRunLoop] currentMode];
112    }
113    if (!runLoopMode) {
114	runLoopMode = NSDefaultRunLoopMode;
115    }
116    return runLoopMode;
117}
118
119/*
120 *----------------------------------------------------------------------
121 *
122 * Tk_MacOSXSetupTkNotifier --
123 *
124 *	This procedure is called during Tk initialization to create
125 *	the event source for TkAqua events.
126 *
127 * Results:
128 *	None.
129 *
130 * Side effects:
131 *	A new event source is created.
132 *
133 *----------------------------------------------------------------------
134 */
135
136void
137Tk_MacOSXSetupTkNotifier(void)
138{
139    TSD_INIT();
140    if (!tsdPtr->initialized) {
141	tsdPtr->initialized = 1;
142
143	/*
144	 * Install TkAqua event source in main event loop thread.
145	 */
146
147	if (CFRunLoopGetMain() == CFRunLoopGetCurrent()) {
148	    if (!pthread_main_np()) {
149		/*
150		 * Panic if main runloop is not on the main application thread.
151		 */
152
153		Tcl_Panic("Tk_MacOSXSetupTkNotifier: %s",
154		    "first [load] of TkAqua has to occur in the main thread!");
155	    }
156	    Tcl_CreateEventSource(TkMacOSXEventsSetupProc,
157		    TkMacOSXEventsCheckProc, GetMainEventQueue());
158	    TkCreateExitHandler(TkMacOSXNotifyExitHandler, NULL);
159	    Tcl_SetServiceMode(TCL_SERVICE_ALL);
160	    TclMacOSXNotifierAddRunLoopMode(NSEventTrackingRunLoopMode);
161	    TclMacOSXNotifierAddRunLoopMode(NSModalPanelRunLoopMode);
162	}
163    }
164}
165
166/*
167 *----------------------------------------------------------------------
168 *
169 * TkMacOSXNotifyExitHandler --
170 *
171 *	This function is called during finalization to clean up the
172 *	TkMacOSXNotify module.
173 *
174 * Results:
175 *	None.
176 *
177 * Side effects:
178 *	None.
179 *
180 *----------------------------------------------------------------------
181 */
182
183static void
184TkMacOSXNotifyExitHandler(
185    ClientData clientData)	/* Not used. */
186{
187    TSD_INIT();
188    Tcl_DeleteEventSource(TkMacOSXEventsSetupProc,
189	    TkMacOSXEventsCheckProc, GetMainEventQueue());
190    tsdPtr->initialized = 0;
191}
192
193/*
194 *----------------------------------------------------------------------
195 *
196 * TkMacOSXEventsSetupProc --
197 *
198 *	This procedure implements the setup part of the TkAqua Events event
199 *	source. It is invoked by Tcl_DoOneEvent before entering the notifier
200 *	to check for events.
201 *
202 * Results:
203 *	None.
204 *
205 * Side effects:
206 *	If TkAqua events are queued, then the maximum block time will be set
207 *	to 0 to ensure that the notifier returns control to Tcl.
208 *
209 *----------------------------------------------------------------------
210 */
211
212static void
213TkMacOSXEventsSetupProc(
214    ClientData clientData,
215    int flags)
216{
217    if (flags & TCL_WINDOW_EVENTS &&
218	    ![[NSRunLoop currentRunLoop] currentMode]) {
219	static Tcl_Time zeroBlockTime = { 0, 0 };
220
221	TSD_INIT();
222	if (!tsdPtr->currentEvent) {
223	    NSEvent *currentEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
224		    untilDate:[NSDate distantPast]
225		    inMode:GetRunLoopMode(TkMacOSXGetModalSession())
226		    dequeue:YES];
227	    if (currentEvent) {
228		tsdPtr->currentEvent =
229			TkMacOSXMakeUncollectableAndRetain(currentEvent);
230	    }
231	}
232	if (tsdPtr->currentEvent) {
233	    Tcl_SetMaxBlockTime(&zeroBlockTime);
234	}
235    }
236}
237
238/*
239 *----------------------------------------------------------------------
240 *
241 * TkMacOSXEventsCheckProc --
242 *
243 *	This procedure processes events sitting in the TkAqua event queue.
244 *
245 * Results:
246 *	None.
247 *
248 * Side effects:
249 *	Moves applicable queued TkAqua events onto the Tcl event queue.
250 *
251 *----------------------------------------------------------------------
252 */
253
254static void
255TkMacOSXEventsCheckProc(
256    ClientData clientData,
257    int flags)
258{
259    if (flags & TCL_WINDOW_EVENTS &&
260	    ![[NSRunLoop currentRunLoop] currentMode]) {
261	NSEvent *currentEvent = nil;
262	NSAutoreleasePool *pool = nil;
263	NSModalSession modalSession;
264
265	TSD_INIT();
266	if (tsdPtr->currentEvent) {
267	    currentEvent = TkMacOSXMakeCollectableAndAutorelease(
268		    tsdPtr->currentEvent);
269	}
270	do {
271	    modalSession = TkMacOSXGetModalSession();
272	    if (!currentEvent) {
273		currentEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
274			untilDate:[NSDate distantPast]
275			inMode:GetRunLoopMode(modalSession) dequeue:YES];
276	    }
277	    if (!currentEvent) {
278		break;
279	    }
280	    [currentEvent retain];
281	    pool = [NSAutoreleasePool new];
282	    if (tkMacOSXGCEnabled) {
283		objc_clear_stack(0);
284	    }
285	    if (![NSApp tkProcessEvent:currentEvent]) {
286		[currentEvent release];
287		currentEvent = nil;
288	    }
289	    if (currentEvent) {
290#ifdef TK_MAC_DEBUG_EVENTS
291		TKLog(@"   event: %@", currentEvent);
292#endif
293		if (modalSession) {
294		    [NSApp _modalSession:modalSession sendEvent:currentEvent];
295		} else {
296		    [NSApp sendEvent:currentEvent];
297		}
298		[currentEvent release];
299		currentEvent = nil;
300	    }
301	    [pool drain];
302	    pool = nil;
303	} while (1);
304    }
305}
306
307/*
308 * Local Variables:
309 * mode: c
310 * c-basic-offset: 4
311 * fill-column: 79
312 * coding: utf-8
313 * End:
314 */
315