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