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  * Copyright 2015 Marc Culler.
11  *
12  * See the file "license.terms" for information on usage and redistribution
13  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14  */
15 
16 #include "tkMacOSXPrivate.h"
17 #include "tkMacOSXEvent.h"
18 #include <tclInt.h>
19 #include <pthread.h>
20 #import <objc/objc-auto.h>
21 
22 /* This is not used for anything at the moment. */
23 typedef struct ThreadSpecificData {
24     int initialized;
25 } ThreadSpecificData;
26 static Tcl_ThreadDataKey dataKey;
27 
28 #define TSD_INIT() ThreadSpecificData *tsdPtr = Tcl_GetThreadData(&dataKey, \
29 	    sizeof(ThreadSpecificData))
30 
31 static void TkMacOSXNotifyExitHandler(ClientData clientData);
32 static void TkMacOSXEventsSetupProc(ClientData clientData, int flags);
33 static void TkMacOSXEventsCheckProc(ClientData clientData, int flags);
34 
35 #pragma mark TKApplication(TKNotify)
36 
37 @interface NSApplication(TKNotify)
38 /* We need to declare this hidden method. */
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 /* Display all windows each time an event is removed from the queue.*/
53 - (NSEvent *)nextEventMatchingMask:(NSUInteger)mask
54 	untilDate:(NSDate *)expiration inMode:(NSString *)mode
55 	dequeue:(BOOL)deqFlag {
56     NSEvent *event = [super nextEventMatchingMask:mask
57 					untilDate:expiration
58 					   inMode:mode
59 					  dequeue:deqFlag];
60     /* Retain this event for later use. Must be released.*/
61     [event retain];
62     [NSApp makeWindowsPerform:@selector(tkDisplayIfNeeded) inOrder:NO];
63     return event;
64 }
65 
66  /*
67  * Call super then check the pasteboard.
68   */
69 - (void)sendEvent:(NSEvent *)theEvent {
70     [super sendEvent:theEvent];
71     [NSApp tkCheckPasteboard];
72 }
73 @end
74 
75 #pragma mark -
76 
77 /*
78  *----------------------------------------------------------------------
79  *
80  * GetRunLoopMode --
81  *
82  * Results:
83  *	RunLoop mode that should be passed to -nextEventMatchingMask:
84  *
85  * Side effects:
86  *	None.
87  *
88  *----------------------------------------------------------------------
89  */
90 
91 static NSString *
GetRunLoopMode(NSModalSession modalSession)92 GetRunLoopMode(NSModalSession modalSession)
93 {
94     NSString *runLoopMode = nil;
95 
96     if (modalSession) {
97 	runLoopMode = NSModalPanelRunLoopMode;
98     } else if (TkMacOSXGetCapture()) {
99 	runLoopMode = NSEventTrackingRunLoopMode;
100     }
101     if (!runLoopMode) {
102 	runLoopMode = [[NSRunLoop currentRunLoop] currentMode];
103     }
104     if (!runLoopMode) {
105 	runLoopMode = NSDefaultRunLoopMode;
106     }
107     return runLoopMode;
108 }
109 
110 /*
111  *----------------------------------------------------------------------
112  *
113  * Tk_MacOSXSetupTkNotifier --
114  *
115  *	This procedure is called during Tk initialization to create
116  *	the event source for TkAqua events.
117  *
118  * Results:
119  *	None.
120  *
121  * Side effects:
122  *	A new event source is created.
123  *
124  *----------------------------------------------------------------------
125  */
126 
127 void
Tk_MacOSXSetupTkNotifier(void)128 Tk_MacOSXSetupTkNotifier(void)
129 {
130     TSD_INIT();
131     if (!tsdPtr->initialized) {
132 	tsdPtr->initialized = 1;
133 
134 	/*
135 	 * Install TkAqua event source in main event loop thread.
136 	 */
137 
138 	if (CFRunLoopGetMain() == CFRunLoopGetCurrent()) {
139 	    if (!pthread_main_np()) {
140 		/*
141 		 * Panic if main runloop is not on the main application thread.
142 		 */
143 
144 		Tcl_Panic("Tk_MacOSXSetupTkNotifier: %s",
145 		    "first [load] of TkAqua has to occur in the main thread!");
146 	    }
147 	    Tcl_CreateEventSource(TkMacOSXEventsSetupProc,
148 				  TkMacOSXEventsCheckProc,
149 				  GetMainEventQueue());
150 	    TkCreateExitHandler(TkMacOSXNotifyExitHandler, NULL);
151 	    Tcl_SetServiceMode(TCL_SERVICE_ALL);
152 	    TclMacOSXNotifierAddRunLoopMode(NSEventTrackingRunLoopMode);
153 	    TclMacOSXNotifierAddRunLoopMode(NSModalPanelRunLoopMode);
154 	}
155     }
156 }
157 
158 /*
159  *----------------------------------------------------------------------
160  *
161  * TkMacOSXNotifyExitHandler --
162  *
163  *	This function is called during finalization to clean up the
164  *	TkMacOSXNotify module.
165  *
166  * Results:
167  *	None.
168  *
169  * Side effects:
170  *	None.
171  *
172  *----------------------------------------------------------------------
173  */
174 
175 static void
TkMacOSXNotifyExitHandler(ClientData clientData)176 TkMacOSXNotifyExitHandler(
177     ClientData clientData)	/* Not used. */
178 {
179     TSD_INIT();
180     Tcl_DeleteEventSource(TkMacOSXEventsSetupProc,
181 			  TkMacOSXEventsCheckProc,
182 			  GetMainEventQueue());
183     tsdPtr->initialized = 0;
184 }
185 
186 /*
187  *----------------------------------------------------------------------
188  *
189  * TkMacOSXEventsSetupProc --
190  *
191  *	This procedure implements the setup part of the MacOSX event
192  *	source. It is invoked by Tcl_DoOneEvent before calling
193  *      TkMacOSXEventsProc to process all queued NSEvents.  In our
194  *      case, all we need to do is to set the Tcl MaxBlockTime to
195  *      0 before starting the loop to process all queued NSEvents.
196  *
197  * Results:
198  *	None.
199  *
200  * Side effects:
201  *
202  *	If NSEvents are queued, then the maximum block time will be set
203  *	to 0 to ensure that control returns immediately to Tcl.
204  *
205  *----------------------------------------------------------------------
206  */
207 
208 static void
TkMacOSXEventsSetupProc(ClientData clientData,int flags)209 TkMacOSXEventsSetupProc(
210     ClientData clientData,
211     int flags)
212 {
213     NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode];
214     /* runloopMode will be nil if we are in the Tcl event loop. */
215     if (flags & TCL_WINDOW_EVENTS && !runloopMode) {
216 	static Tcl_Time zeroBlockTime = { 0, 0 };
217 	/* Call this with dequeue=NO -- just checking if the queue is empty. */
218 	NSEvent *currentEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
219 				       untilDate:[NSDate distantPast]
220 				       inMode:GetRunLoopMode(TkMacOSXGetModalSession())
221 				       dequeue:NO];
222 	if (currentEvent) {
223 	    if (currentEvent.type > 0) {
224 		Tcl_SetMaxBlockTime(&zeroBlockTime);
225 	    }
226 	    [currentEvent release];
227 	}
228     }
229 }
230 
231 /*
232  *----------------------------------------------------------------------
233  *
234  * TkMacOSXEventsCheckProc --
235  *
236  *	This procedure loops through all NSEvents waiting in the
237  *      TKApplication event queue, generating X events from them.
238  *
239  * Results:
240  *	None.
241  *
242  * Side effects:
243  *	NSevents are used to generate X events, which are added to the
244  *      Tcl event queue.
245  *
246  *----------------------------------------------------------------------
247  */
248 static void
TkMacOSXEventsCheckProc(ClientData clientData,int flags)249 TkMacOSXEventsCheckProc(
250     ClientData clientData,
251     int flags)
252 {
253     NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode];
254     /* runloopMode will be nil if we are in the Tcl event loop. */
255     if (flags & TCL_WINDOW_EVENTS && !runloopMode) {
256 	NSEvent *currentEvent = nil;
257 	NSEvent *testEvent = nil;
258 	NSModalSession modalSession;
259 
260 	do {
261 	    [NSApp _resetAutoreleasePool];
262 	    modalSession = TkMacOSXGetModalSession();
263 	    testEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
264 					      untilDate:[NSDate distantPast]
265 						 inMode:GetRunLoopMode(modalSession)
266 						dequeue:NO];
267 	    /* We must not steal any events during LiveResize. */
268 	    if (testEvent && [[testEvent window] inLiveResize]) {
269 		break;
270 	    }
271 
272 	    currentEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
273 					      untilDate:[NSDate distantPast]
274 						 inMode:GetRunLoopMode(modalSession)
275 						dequeue:YES];
276 	    if (currentEvent) {
277 		/* Generate Xevents. */
278 		int oldServiceMode = Tcl_SetServiceMode(TCL_SERVICE_ALL);
279 		NSEvent *processedEvent = [NSApp tkProcessEvent:currentEvent];
280 		Tcl_SetServiceMode(oldServiceMode);
281 		if (processedEvent) { /* Should always be non-NULL. */
282 #ifdef TK_MAC_DEBUG_EVENTS
283 		    TKLog(@"   event: %@", currentEvent);
284 #endif
285 		    if (modalSession) {
286 			[NSApp _modalSession:modalSession sendEvent:currentEvent];
287 		    } else {
288 			[NSApp sendEvent:currentEvent];
289 		    }
290 		}
291 		[currentEvent release];
292 	    } else {
293 		break;
294 	    }
295 	} while (1);
296     }
297 }
298 
299 
300 /*
301  * Local Variables:
302  * mode: objc
303  * c-basic-offset: 4
304  * fill-column: 79
305  * coding: utf-8
306  * End:
307  */
308