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