1 /* 2 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #import <pthread.h> 27 #import <objc/runtime.h> 28 #import <Cocoa/Cocoa.h> 29 #import <Security/AuthSession.h> 30 #import <JavaNativeFoundation/JavaNativeFoundation.h> 31 #import <JavaRuntimeSupport/JavaRuntimeSupport.h> 32 33 #import "NSApplicationAWT.h" 34 #import "PropertiesUtilities.h" 35 #import "ThreadUtilities.h" 36 #import "AWT_debug.h" 37 #import "ApplicationDelegate.h" 38 39 #define DEBUG 0 40 41 42 // The symbol is defined in libosxapp.dylib (ThreadUtilities.m) 43 extern JavaVM *jvm; 44 45 // Indicates if AWT is running embedded (in SWT, FX, elsewhere) 46 static BOOL isEmbedded = NO; 47 48 static bool ShouldPrintVerboseDebugging() { 49 static int debug = -1; 50 if (debug == -1) { 51 debug = (int)(getenv("JAVA_AWT_VERBOSE") != NULL) || (DEBUG != 0); 52 } 53 return (bool)debug; 54 } 55 56 // This is the data necessary to have JNI_OnLoad wait for AppKit to start. 57 static BOOL sAppKitStarted = NO; 58 static pthread_mutex_t sAppKitStarted_mutex = PTHREAD_MUTEX_INITIALIZER; 59 static pthread_cond_t sAppKitStarted_cv = PTHREAD_COND_INITIALIZER; 60 61 void setBusy(BOOL isBusy); 62 static void BusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg); 63 static void NotBusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg); 64 static void AWT_NSUncaughtExceptionHandler(NSException *exception); 65 66 static CFRunLoopObserverRef busyObserver = NULL; 67 static CFRunLoopObserverRef notBusyObserver = NULL; 68 69 static void setUpAWTAppKit(JNIEnv *env) 70 { 71 // Add CFRunLoopObservers to call into AWT so that AWT knows that the 72 // AWT thread (which is the AppKit main thread) is alive. This way AWT 73 // will not automatically shutdown. 74 busyObserver = CFRunLoopObserverCreate( 75 NULL, // CFAllocator 76 kCFRunLoopAfterWaiting, // CFOptionFlags 77 true, // repeats 78 NSIntegerMax, // order 79 &BusyObserver, // CFRunLoopObserverCallBack 80 NULL); // CFRunLoopObserverContext 81 82 notBusyObserver = CFRunLoopObserverCreate( 83 NULL, // CFAllocator 84 kCFRunLoopBeforeWaiting, // CFOptionFlags 85 true, // repeats 86 NSIntegerMin, // order 87 &NotBusyObserver, // CFRunLoopObserverCallBack 88 NULL); // CFRunLoopObserverContext 89 90 CFRunLoopRef runLoop = [[NSRunLoop currentRunLoop] getCFRunLoop]; 91 CFRunLoopAddObserver(runLoop, busyObserver, kCFRunLoopDefaultMode); 92 CFRunLoopAddObserver(runLoop, notBusyObserver, kCFRunLoopDefaultMode); 93 94 CFRelease(busyObserver); 95 CFRelease(notBusyObserver); 96 97 setBusy(YES); 98 99 100 // Set the java name of the AppKit main thread appropriately. 101 jclass threadClass = NULL; 102 jstring name = NULL; 103 jobject curThread = NULL; 104 105 threadClass = (*env)->FindClass(env, "java/lang/Thread"); 106 if (threadClass == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 107 jmethodID currentThreadID = (*env)->GetStaticMethodID(env, threadClass, "currentThread", "()Ljava/lang/Thread;"); 108 if (currentThreadID == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 109 jmethodID setName = (*env)->GetMethodID(env, threadClass, "setName", "(Ljava/lang/String;)V"); 110 if (setName == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 111 112 curThread = (*env)->CallStaticObjectMethod(env, threadClass, currentThreadID); // AWT_THREADING Safe (known object) 113 if (curThread == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 114 name = (*env)->NewStringUTF(env, "AWT-AppKit"); 115 if (name == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 116 (*env)->CallVoidMethod(env, curThread, setName, name); // AWT_THREADING Safe (known object) 117 if ((*env)->ExceptionCheck(env)) goto cleanup; 118 119 cleanup: 120 if (threadClass != NULL) { 121 (*env)->DeleteLocalRef(env, threadClass); 122 } 123 if (name != NULL) { 124 (*env)->DeleteLocalRef(env, name); 125 } 126 if (curThread != NULL) { 127 (*env)->DeleteLocalRef(env, curThread); 128 } 129 if ((*env)->ExceptionCheck(env)) { 130 (*env)->ExceptionDescribe(env); 131 (*env)->ExceptionClear(env); 132 } 133 } 134 135 136 // Returns true if java believes it is running headless 137 BOOL isHeadless(JNIEnv *env) { 138 // Just access the property directly, instead of using GraphicsEnvironment.isHeadless. 139 // This is because this may be called while AWT is being loaded, and calling AWT 140 // while it is being loaded will deadlock. 141 static JNF_CLASS_CACHE(jc_Toolkit, "java/awt/GraphicsEnvironment"); 142 static JNF_STATIC_MEMBER_CACHE(jm_isHeadless, jc_Toolkit, "isHeadless", "()Z"); 143 return JNFCallStaticBooleanMethod(env, jm_isHeadless); 144 } 145 146 void setBusy(BOOL busy) { 147 AWT_ASSERT_APPKIT_THREAD; 148 149 JNIEnv *env = [ThreadUtilities getJNIEnv]; 150 static JNF_CLASS_CACHE(jc_AWTAutoShutdown, "sun/awt/AWTAutoShutdown"); 151 152 if (busy) { 153 static JNF_STATIC_MEMBER_CACHE(jm_notifyBusyMethod, jc_AWTAutoShutdown, "notifyToolkitThreadBusy", "()V"); 154 JNFCallStaticVoidMethod(env, jm_notifyBusyMethod); 155 } else { 156 static JNF_STATIC_MEMBER_CACHE(jm_notifyFreeMethod, jc_AWTAutoShutdown, "notifyToolkitThreadFree", "()V"); 157 JNFCallStaticVoidMethod(env, jm_notifyFreeMethod); 158 } 159 } 160 161 static void BusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg) { 162 AWT_ASSERT_APPKIT_THREAD; 163 164 // This is only called with the selector kCFRunLoopAfterWaiting. 165 #ifndef PRODUCT_BUILD 166 assert(what == kCFRunLoopAfterWaiting); 167 #endif /* PRODUCT_BUILD */ 168 169 setBusy(YES); 170 } 171 172 static void NotBusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg) { 173 AWT_ASSERT_APPKIT_THREAD; 174 175 // This is only called with the selector kCFRunLoopBeforeWaiting. 176 #ifndef PRODUCT_BUILD 177 assert(what == kCFRunLoopBeforeWaiting); 178 #endif /* PRODUCT_BUILD */ 179 180 setBusy(NO); 181 } 182 183 static void AWT_NSUncaughtExceptionHandler(NSException *exception) { 184 NSLog(@"Apple AWT Internal Exception: %@", [exception description]); 185 } 186 187 // This is an empty Obj-C object just so that -peformSelectorOnMainThread can be used. 188 @interface AWTStarter : NSObject { } 189 + (void)start:(BOOL)headless; 190 - (void)starter:(NSArray*)args; 191 + (void)appKitIsRunning:(id)arg; 192 @end 193 194 @implementation AWTStarter 195 196 + (BOOL) isConnectedToWindowServer { 197 SecuritySessionId session_id; 198 SessionAttributeBits session_info; 199 OSStatus status = SessionGetInfo(callerSecuritySession, &session_id, &session_info); 200 if (status != noErr) return NO; 201 if (!(session_info & sessionHasGraphicAccess)) return NO; 202 return YES; 203 } 204 205 + (BOOL) markAppAsDaemon { 206 id jrsAppKitAWTClass = objc_getClass("JRSAppKitAWT"); 207 SEL markAppSel = @selector(markAppIsDaemon); 208 if (![jrsAppKitAWTClass respondsToSelector:markAppSel]) return NO; 209 return (BOOL)[jrsAppKitAWTClass performSelector:markAppSel]; 210 } 211 212 + (void)appKitIsRunning:(id)arg { 213 // Headless: NO 214 // Embedded: BOTH 215 // Multiple Calls: NO 216 // Callers: AppKit's NSApplicationDidFinishLaunchingNotification or +[AWTStarter startAWT:] 217 AWT_ASSERT_APPKIT_THREAD; 218 219 BOOL verbose = ShouldPrintVerboseDebugging(); 220 if (verbose) AWT_DEBUG_LOG(@"about to message AppKit started"); 221 222 // Signal that AppKit has started (or is already running). 223 pthread_mutex_lock(&sAppKitStarted_mutex); 224 sAppKitStarted = YES; 225 pthread_cond_signal(&sAppKitStarted_cv); 226 pthread_mutex_unlock(&sAppKitStarted_mutex); 227 228 if (verbose) AWT_DEBUG_LOG(@"finished messaging AppKit started"); 229 } 230 231 + (void)start:(BOOL)headless 232 { 233 BOOL verbose = ShouldPrintVerboseDebugging(); 234 235 // Headless: BOTH 236 // Embedded: BOTH 237 // Multiple Calls: NO 238 // Caller: JNI_OnLoad 239 240 // onMainThread is NOT the same at SWT mode! 241 // If the JVM was started on the first thread for SWT, but the SWT loads the AWT on a secondary thread, 242 // onMainThread here will be false but SWT mode will be true. If we are currently on the main thread, we don't 243 // need to throw AWT startup over to another thread. 244 BOOL onMainThread = (pthread_main_np() != 0); 245 246 if (verbose) { 247 NSString *msg = [NSString stringWithFormat:@"+[AWTStarter start headless:%d] { onMainThread:%d }", headless, onMainThread]; 248 AWT_DEBUG_LOG(msg); 249 } 250 251 if (!headless) 252 { 253 // Listen for the NSApp to start. This indicates that JNI_OnLoad can proceed. 254 // It must wait because there is a chance that another java thread will grab 255 // the AppKit lock before the +[NSApplication sharedApplication] returns. 256 // See <rdar://problem/3492666> for an example. 257 [[NSNotificationCenter defaultCenter] addObserver:[AWTStarter class] 258 selector:@selector(appKitIsRunning:) 259 name:NSApplicationDidFinishLaunchingNotification 260 object:nil]; 261 262 if (verbose) NSLog(@"+[AWTStarter start:::]: registered NSApplicationDidFinishLaunchingNotification"); 263 } 264 265 id st = [[AWTStarter alloc] init]; 266 267 NSArray * args = [NSArray arrayWithObjects: 268 [NSNumber numberWithBool: onMainThread], 269 [NSNumber numberWithBool: headless], 270 [NSNumber numberWithBool: verbose], 271 nil]; 272 273 if (onMainThread) { 274 [st starter:args]; 275 } else { 276 [st performSelectorOnMainThread: @selector(starter:) withObject:args waitUntilDone:NO]; 277 } 278 279 if (!headless && !onMainThread) { 280 if (verbose) AWT_DEBUG_LOG(@"about to wait on AppKit startup mutex"); 281 282 // Wait here for AppKit to have started (or for AWT to have been loaded into 283 // an already running NSApplication). 284 pthread_mutex_lock(&sAppKitStarted_mutex); 285 while (sAppKitStarted == NO) { 286 pthread_cond_wait(&sAppKitStarted_cv, &sAppKitStarted_mutex); 287 } 288 pthread_mutex_unlock(&sAppKitStarted_mutex); 289 290 // AWT gets here AFTER +[AWTStarter appKitIsRunning:] is called. 291 if (verbose) AWT_DEBUG_LOG(@"got out of the AppKit startup mutex"); 292 } 293 294 // Don't set the delegate until the NSApplication has been created and 295 // its finishLaunching has initialized it. 296 // ApplicationDelegate is the support code for com.apple.eawt. 297 void (^setDelegateBlock)() = ^(){ 298 OSXAPP_SetApplicationDelegate([ApplicationDelegate sharedDelegate]); 299 }; 300 if (onMainThread) { 301 setDelegateBlock(); 302 } else { 303 [JNFRunLoop performOnMainThreadWaiting:YES withBlock:setDelegateBlock]; 304 } 305 } 306 307 - (void)starter:(NSArray*)args { 308 NSAutoreleasePool *pool = [NSAutoreleasePool new]; 309 310 BOOL onMainThread = [[args objectAtIndex:0] boolValue]; 311 BOOL headless = [[args objectAtIndex:1] boolValue]; 312 BOOL verbose = [[args objectAtIndex:2] boolValue]; 313 314 BOOL wasOnMainThread = onMainThread; 315 316 // Add the exception handler of last resort 317 NSSetUncaughtExceptionHandler(AWT_NSUncaughtExceptionHandler); 318 319 // Headless mode trumps either ordinary AWT or SWT-in-AWT mode. Declare us a daemon and return. 320 if (headless) { 321 BOOL didBecomeDaemon = [AWTStarter markAppAsDaemon]; 322 return; 323 } 324 325 // This will create a NSApplicationAWT for standalone AWT programs, unless there is 326 // already a NSApplication instance. If there is already a NSApplication instance, 327 // and -[NSApplication isRunning] returns YES, AWT is embedded inside another 328 // AppKit Application. 329 NSApplication *app = [NSApplicationAWT sharedApplication]; 330 isEmbedded = ![NSApp isKindOfClass:[NSApplicationAWT class]]; 331 332 JNIEnv *env = [ThreadUtilities getJNIEnv]; 333 if (isEmbedded) { 334 static JNF_CLASS_CACHE(jc_AWTKeepAlive, "sun/lwawt/macosx/AWTKeepAlive"); 335 static JNF_STATIC_MEMBER_CACHE(jm_activate, jc_AWTKeepAlive, "activate", "()V"); 336 337 // This will send native events every 0.5 s, thus letting the embedder know 338 // that AWT is alive. The events are only sent while there are active 339 // AWT windows present. 340 JNFCallStaticVoidMethod(env, jm_activate); 341 } else { 342 // Install run loop observers and set the AppKit Java thread name 343 setUpAWTAppKit(env); 344 } 345 346 // AWT gets to this point BEFORE NSApplicationDidFinishLaunchingNotification is sent. 347 if (![app isRunning]) { 348 if (verbose) AWT_DEBUG_LOG(@"+[AWTStarter startAWT]: ![app isRunning]"); 349 350 // Note that in theory we can get here even if we're embedded and an 351 // embedder isn't running an event loop yet for some reason. However, 352 // it seems to be very unlikely. In any case, the runAWTLoopWithApp is 353 // able to deal with it anyway. Whether the embedder can deal with 354 // this is unknown. Also, it's unclear how the event loop is going to 355 // be terminated in this case. Setting up observers is not an option 356 // since the embedder could force termination. We wouldn't know and 357 // could crash when calling to Java. 358 359 // This is where the AWT AppKit thread parks itself to process events. 360 [NSApplicationAWT runAWTLoopWithApp: app]; 361 } else { 362 // We're either embedded, or showing a splash screen 363 if (isEmbedded) { 364 if (verbose) AWT_DEBUG_LOG(@"running embedded"); 365 366 // We don't track if the runloop is busy, so set it free to let AWT finish when it needs 367 setBusy(NO); 368 } else { 369 if (verbose) AWT_DEBUG_LOG(@"running after showing a splash screen"); 370 } 371 372 // Signal so that JNI_OnLoad can proceed. 373 if (!wasOnMainThread) [AWTStarter appKitIsRunning:nil]; 374 375 // Proceed to exit this call as there is no reason to run the NSApplication event loop. 376 } 377 378 [pool drain]; 379 } 380 381 @end 382 383 384 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { 385 BOOL verbose = ShouldPrintVerboseDebugging(); 386 if (verbose) AWT_DEBUG_LOG(@"entered JNI_OnLoad"); 387 388 // Headless: BOTH 389 // Embedded: BOTH 390 // Multiple Calls: NO 391 // Caller: JavaVM classloader 392 393 // Keep a static reference for other archives. 394 OSXAPP_SetJavaVM(vm); 395 396 JNIEnv *env = NULL; 397 398 // Need JNIEnv for JNF_COCOA_ENTER(env); macro below 399 jint status = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4); 400 if (status != JNI_OK || env == NULL) { 401 AWT_DEBUG_LOG(@"Can't get JNIEnv"); 402 return JNI_VERSION_1_4; 403 } 404 405 JNF_COCOA_ENTER(env); 406 407 char envVar[80]; 408 snprintf(envVar, sizeof(envVar), "JAVA_STARTED_ON_FIRST_THREAD_%d", getpid()); 409 if (getenv(envVar) != NULL) { 410 // If we don't need this in the long term, it makes sense to not set 411 // this env variable in the launcher code in the first place 412 unsetenv(envVar); 413 } 414 415 BOOL headless = isHeadless(env); 416 417 // We need to let Foundation know that this is a multithreaded application, if it isn't already. 418 if (![NSThread isMultiThreaded]) { 419 [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]; 420 } 421 422 [AWTStarter start:headless]; 423 424 JNF_COCOA_EXIT(env); 425 426 if (verbose) AWT_DEBUG_LOG(@"exiting JNI_OnLoad"); 427 428 return JNI_VERSION_1_4; 429 }