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 // Indicates that the app has been started with -XstartOnFirstThread 49 // (directly or via WebStart settings), and AWT should not run its 50 // own event loop in this mode. Even if a loop isn't running yet, 51 // we expect an embedder (e.g. SWT) to start it some time later. 52 static BOOL forceEmbeddedMode = NO; 53 54 static bool ShouldPrintVerboseDebugging() { 55 static int debug = -1; 56 if (debug == -1) { 57 debug = (int)(getenv("JAVA_AWT_VERBOSE") != NULL) || (DEBUG != 0); 58 } 59 return (bool)debug; 60 } 61 62 // This is the data necessary to have JNI_OnLoad wait for AppKit to start. 63 static BOOL sAppKitStarted = NO; 64 static pthread_mutex_t sAppKitStarted_mutex = PTHREAD_MUTEX_INITIALIZER; 65 static pthread_cond_t sAppKitStarted_cv = PTHREAD_COND_INITIALIZER; 66 67 void setBusy(BOOL isBusy); 68 static void BusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg); 69 static void NotBusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg); 70 static void AWT_NSUncaughtExceptionHandler(NSException *exception); 71 72 static CFRunLoopObserverRef busyObserver = NULL; 73 static CFRunLoopObserverRef notBusyObserver = NULL; 74 75 static void setUpAWTAppKit() 76 { 77 BOOL verbose = ShouldPrintVerboseDebugging(); 78 if (verbose) AWT_DEBUG_LOG(@"setting up busy observers"); 79 80 // Add CFRunLoopObservers to call into AWT so that AWT knows that the 81 // AWT thread (which is the AppKit main thread) is alive. This way AWT 82 // will not automatically shutdown. 83 busyObserver = CFRunLoopObserverCreate( 84 NULL, // CFAllocator 85 kCFRunLoopAfterWaiting, // CFOptionFlags 86 true, // repeats 87 NSIntegerMax, // order 88 &BusyObserver, // CFRunLoopObserverCallBack 89 NULL); // CFRunLoopObserverContext 90 91 notBusyObserver = CFRunLoopObserverCreate( 92 NULL, // CFAllocator 93 kCFRunLoopBeforeWaiting, // CFOptionFlags 94 true, // repeats 95 NSIntegerMin, // order 96 &NotBusyObserver, // CFRunLoopObserverCallBack 97 NULL); // CFRunLoopObserverContext 98 99 CFRunLoopRef runLoop = [[NSRunLoop currentRunLoop] getCFRunLoop]; 100 CFRunLoopAddObserver(runLoop, busyObserver, kCFRunLoopDefaultMode); 101 CFRunLoopAddObserver(runLoop, notBusyObserver, kCFRunLoopDefaultMode); 102 103 CFRelease(busyObserver); 104 CFRelease(notBusyObserver); 105 106 setBusy(YES); 107 } 108 109 static void setUpAppKitThreadName() 110 { 111 BOOL verbose = ShouldPrintVerboseDebugging(); 112 JNIEnv *env = [ThreadUtilities getJNIEnv]; 113 114 // Set the java name of the AppKit main thread appropriately. 115 jclass threadClass = NULL; 116 jstring name = NULL; 117 jobject curThread = NULL; 118 119 threadClass = (*env)->FindClass(env, "java/lang/Thread"); 120 if (threadClass == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 121 jmethodID currentThreadID = (*env)->GetStaticMethodID(env, threadClass, "currentThread", "()Ljava/lang/Thread;"); 122 if (currentThreadID == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 123 jmethodID setName = (*env)->GetMethodID(env, threadClass, "setName", "(Ljava/lang/String;)V"); 124 if (setName == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 125 126 curThread = (*env)->CallStaticObjectMethod(env, threadClass, currentThreadID); // AWT_THREADING Safe (known object) 127 if (curThread == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 128 name = (*env)->NewStringUTF(env, "AWT-AppKit"); 129 if (name == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 130 (*env)->CallVoidMethod(env, curThread, setName, name); // AWT_THREADING Safe (known object) 131 if ((*env)->ExceptionCheck(env)) goto cleanup; 132 133 cleanup: 134 if (threadClass != NULL) { 135 (*env)->DeleteLocalRef(env, threadClass); 136 } 137 if (name != NULL) { 138 (*env)->DeleteLocalRef(env, name); 139 } 140 if (curThread != NULL) { 141 (*env)->DeleteLocalRef(env, curThread); 142 } 143 if ((*env)->ExceptionCheck(env)) { 144 (*env)->ExceptionDescribe(env); 145 (*env)->ExceptionClear(env); 146 } 147 148 if (verbose) AWT_DEBUG_LOG(@"finished setting thread name"); 149 } 150 151 152 // Returns true if java believes it is running headless 153 BOOL isHeadless(JNIEnv *env) { 154 // Just access the property directly, instead of using GraphicsEnvironment.isHeadless. 155 // This is because this may be called while AWT is being loaded, and calling AWT 156 // while it is being loaded will deadlock. 157 static JNF_CLASS_CACHE(jc_Toolkit, "java/awt/GraphicsEnvironment"); 158 static JNF_STATIC_MEMBER_CACHE(jm_isHeadless, jc_Toolkit, "isHeadless", "()Z"); 159 return JNFCallStaticBooleanMethod(env, jm_isHeadless); 160 } 161 162 BOOL isSWTInWebStart(JNIEnv* env) { 163 NSString *swtWebStart = [PropertiesUtilities javaSystemPropertyForKey:@"com.apple.javaws.usingSWT" withEnv:env]; 164 return [@"true" isCaseInsensitiveLike:swtWebStart]; 165 } 166 167 void setBusy(BOOL busy) { 168 AWT_ASSERT_APPKIT_THREAD; 169 170 JNIEnv *env = [ThreadUtilities getJNIEnv]; 171 static JNF_CLASS_CACHE(jc_AWTAutoShutdown, "sun/awt/AWTAutoShutdown"); 172 173 if (busy) { 174 static JNF_STATIC_MEMBER_CACHE(jm_notifyBusyMethod, jc_AWTAutoShutdown, "notifyToolkitThreadBusy", "()V"); 175 JNFCallStaticVoidMethod(env, jm_notifyBusyMethod); 176 } else { 177 static JNF_STATIC_MEMBER_CACHE(jm_notifyFreeMethod, jc_AWTAutoShutdown, "notifyToolkitThreadFree", "()V"); 178 JNFCallStaticVoidMethod(env, jm_notifyFreeMethod); 179 } 180 } 181 182 static void BusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg) { 183 AWT_ASSERT_APPKIT_THREAD; 184 185 // This is only called with the selector kCFRunLoopAfterWaiting. 186 #ifndef PRODUCT_BUILD 187 assert(what == kCFRunLoopAfterWaiting); 188 #endif /* PRODUCT_BUILD */ 189 190 setBusy(YES); 191 } 192 193 static void NotBusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg) { 194 AWT_ASSERT_APPKIT_THREAD; 195 196 // This is only called with the selector kCFRunLoopBeforeWaiting. 197 #ifndef PRODUCT_BUILD 198 assert(what == kCFRunLoopBeforeWaiting); 199 #endif /* PRODUCT_BUILD */ 200 201 setBusy(NO); 202 } 203 204 static void AWT_NSUncaughtExceptionHandler(NSException *exception) { 205 NSLog(@"Apple AWT Internal Exception: %@", [exception description]); 206 } 207 208 // This is an empty Obj-C object just so that -peformSelectorOnMainThread can be used. 209 @interface AWTStarter : NSObject { } 210 + (void)start:(BOOL)headless; 211 - (void)starter:(NSArray*)args; 212 + (void)appKitIsRunning:(id)arg; 213 @end 214 215 @implementation AWTStarter 216 217 + (BOOL) isConnectedToWindowServer { 218 SecuritySessionId session_id; 219 SessionAttributeBits session_info; 220 OSStatus status = SessionGetInfo(callerSecuritySession, &session_id, &session_info); 221 if (status != noErr) return NO; 222 if (!(session_info & sessionHasGraphicAccess)) return NO; 223 return YES; 224 } 225 226 + (BOOL) markAppAsDaemon { 227 id jrsAppKitAWTClass = objc_getClass("JRSAppKitAWT"); 228 SEL markAppSel = @selector(markAppIsDaemon); 229 if (![jrsAppKitAWTClass respondsToSelector:markAppSel]) return NO; 230 return (BOOL)[jrsAppKitAWTClass performSelector:markAppSel]; 231 } 232 233 + (void)appKitIsRunning:(id)arg { 234 // Headless: NO 235 // Embedded: BOTH 236 // Multiple Calls: NO 237 // Callers: AppKit's NSApplicationDidFinishLaunchingNotification or +[AWTStarter startAWT:] 238 AWT_ASSERT_APPKIT_THREAD; 239 240 BOOL verbose = ShouldPrintVerboseDebugging(); 241 if (verbose) AWT_DEBUG_LOG(@"about to message AppKit started"); 242 243 // Signal that AppKit has started (or is already running). 244 pthread_mutex_lock(&sAppKitStarted_mutex); 245 sAppKitStarted = YES; 246 pthread_cond_signal(&sAppKitStarted_cv); 247 pthread_mutex_unlock(&sAppKitStarted_mutex); 248 249 if (verbose) AWT_DEBUG_LOG(@"finished messaging AppKit started"); 250 } 251 252 + (void)start:(BOOL)headless 253 { 254 BOOL verbose = ShouldPrintVerboseDebugging(); 255 256 // Headless: BOTH 257 // Embedded: BOTH 258 // Multiple Calls: NO 259 // Caller: JNI_OnLoad 260 261 // onMainThread is NOT the same at SWT mode! 262 // If the JVM was started on the first thread for SWT, but the SWT loads the AWT on a secondary thread, 263 // onMainThread here will be false but SWT mode will be true. If we are currently on the main thread, we don't 264 // need to throw AWT startup over to another thread. 265 BOOL onMainThread = (pthread_main_np() != 0); 266 267 if (verbose) { 268 NSString *msg = [NSString stringWithFormat:@"+[AWTStarter start headless:%d] { onMainThread:%d }", headless, onMainThread]; 269 AWT_DEBUG_LOG(msg); 270 } 271 272 if (!headless) 273 { 274 // Listen for the NSApp to start. This indicates that JNI_OnLoad can proceed. 275 // It must wait because there is a chance that another java thread will grab 276 // the AppKit lock before the +[NSApplication sharedApplication] returns. 277 // See <rdar://problem/3492666> for an example. 278 [[NSNotificationCenter defaultCenter] addObserver:[AWTStarter class] 279 selector:@selector(appKitIsRunning:) 280 name:NSApplicationDidFinishLaunchingNotification 281 object:nil]; 282 283 if (verbose) NSLog(@"+[AWTStarter start:::]: registered NSApplicationDidFinishLaunchingNotification"); 284 } 285 286 id st = [[AWTStarter alloc] init]; 287 288 NSArray * args = [NSArray arrayWithObjects: 289 [NSNumber numberWithBool: onMainThread], 290 [NSNumber numberWithBool: headless], 291 [NSNumber numberWithBool: verbose], 292 nil]; 293 294 if (onMainThread) { 295 [st starter:args]; 296 } else { 297 [st performSelectorOnMainThread: @selector(starter:) withObject:args waitUntilDone:NO]; 298 } 299 300 if (!headless && !onMainThread) { 301 if (verbose) AWT_DEBUG_LOG(@"about to wait on AppKit startup mutex"); 302 303 // Wait here for AppKit to have started (or for AWT to have been loaded into 304 // an already running NSApplication). 305 pthread_mutex_lock(&sAppKitStarted_mutex); 306 while (sAppKitStarted == NO) { 307 pthread_cond_wait(&sAppKitStarted_cv, &sAppKitStarted_mutex); 308 } 309 pthread_mutex_unlock(&sAppKitStarted_mutex); 310 311 // AWT gets here AFTER +[AWTStarter appKitIsRunning:] is called. 312 if (verbose) AWT_DEBUG_LOG(@"got out of the AppKit startup mutex"); 313 } 314 315 // Don't set the delegate until the NSApplication has been created and 316 // its finishLaunching has initialized it. 317 // ApplicationDelegate is the support code for com.apple.eawt. 318 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 319 OSXAPP_SetApplicationDelegate([ApplicationDelegate sharedDelegate]); 320 }]; 321 } 322 323 - (void)starter:(NSArray*)args { 324 NSAutoreleasePool *pool = [NSAutoreleasePool new]; 325 326 BOOL onMainThread = [[args objectAtIndex:0] boolValue]; 327 BOOL headless = [[args objectAtIndex:1] boolValue]; 328 BOOL verbose = [[args objectAtIndex:2] boolValue]; 329 330 BOOL wasOnMainThread = onMainThread; 331 332 // Add the exception handler of last resort 333 NSSetUncaughtExceptionHandler(AWT_NSUncaughtExceptionHandler); 334 335 // Headless mode trumps either ordinary AWT or SWT-in-AWT mode. Declare us a daemon and return. 336 if (headless) { 337 if (!forceEmbeddedMode) { 338 setUpAppKitThreadName(); 339 } 340 [AWTStarter markAppAsDaemon]; 341 return; 342 } 343 344 if (forceEmbeddedMode) { 345 if (verbose) NSLog(@"in SWT or SWT/WebStart mode"); 346 347 // Init a default NSApplication instance instead of the NSApplicationAWT. 348 // Note that [NSApp isRunning] will return YES after that, though 349 // this behavior isn't specified anywhere. We rely on that. 350 NSApplicationLoad(); 351 } 352 353 // This will create a NSApplicationAWT for standalone AWT programs, unless there is 354 // already a NSApplication instance. If there is already a NSApplication instance, 355 // and -[NSApplication isRunning] returns YES, AWT is embedded inside another 356 // AppKit Application. 357 NSApplication *app = [NSApplicationAWT sharedApplication]; 358 isEmbedded = ![NSApp isKindOfClass:[NSApplicationAWT class]]; 359 360 if (!isEmbedded) { 361 // Install run loop observers and set the AppKit Java thread name 362 setUpAWTAppKit(); 363 setUpAppKitThreadName(); 364 } 365 366 // AWT gets to this point BEFORE NSApplicationDidFinishLaunchingNotification is sent. 367 if (![app isRunning]) { 368 if (verbose) AWT_DEBUG_LOG(@"+[AWTStarter startAWT]: ![app isRunning]"); 369 370 // This is where the AWT AppKit thread parks itself to process events. 371 [NSApplicationAWT runAWTLoopWithApp: app]; 372 } else { 373 // We're either embedded, or showing a splash screen 374 if (isEmbedded) { 375 if (verbose) AWT_DEBUG_LOG(@"running embedded"); 376 377 // We don't track if the runloop is busy, so set it free to let AWT finish when it needs 378 setBusy(NO); 379 } else { 380 if (verbose) AWT_DEBUG_LOG(@"running after showing a splash screen"); 381 } 382 383 // Signal so that JNI_OnLoad can proceed. 384 if (!wasOnMainThread) [AWTStarter appKitIsRunning:nil]; 385 386 // Proceed to exit this call as there is no reason to run the NSApplication event loop. 387 } 388 389 [pool drain]; 390 } 391 392 @end 393 394 395 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { 396 BOOL verbose = ShouldPrintVerboseDebugging(); 397 if (verbose) AWT_DEBUG_LOG(@"entered JNI_OnLoad"); 398 399 // Headless: BOTH 400 // Embedded: BOTH 401 // Multiple Calls: NO 402 // Caller: JavaVM classloader 403 404 // Keep a static reference for other archives. 405 OSXAPP_SetJavaVM(vm); 406 407 JNIEnv *env = NULL; 408 409 // Need JNIEnv for JNF_COCOA_ENTER(env); macro below 410 jint status = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4); 411 if (status != JNI_OK || env == NULL) { 412 AWT_DEBUG_LOG(@"Can't get JNIEnv"); 413 return JNI_VERSION_1_4; 414 } 415 416 JNF_COCOA_ENTER(env); 417 418 // Launcher sets this env variable if -XstartOnFirstThread is specified 419 char envVar[80]; 420 snprintf(envVar, sizeof(envVar), "JAVA_STARTED_ON_FIRST_THREAD_%d", getpid()); 421 if (getenv(envVar) != NULL) { 422 forceEmbeddedMode = YES; 423 unsetenv(envVar); 424 } 425 426 if (isSWTInWebStart(env)) { 427 forceEmbeddedMode = YES; 428 } 429 430 BOOL headless = isHeadless(env); 431 432 // We need to let Foundation know that this is a multithreaded application, if it isn't already. 433 if (![NSThread isMultiThreaded]) { 434 [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]; 435 } 436 437 [AWTStarter start:headless]; 438 439 JNF_COCOA_EXIT(env); 440 441 if (verbose) AWT_DEBUG_LOG(@"exiting JNI_OnLoad"); 442 443 return JNI_VERSION_1_4; 444 }