1 /* 2 * Copyright (c) 2011, 2013, 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 [jrsAppKitAWTClass performSelector:markAppSel] ? YES : NO; 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 if (!headless) { 316 // Don't set the delegate until the NSApplication has been created and 317 // its finishLaunching has initialized it. 318 // ApplicationDelegate is the support code for com.apple.eawt. 319 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 320 id<NSApplicationDelegate> delegate = [ApplicationDelegate sharedDelegate]; 321 if (delegate != nil) { 322 OSXAPP_SetApplicationDelegate(delegate); 323 } 324 }]; 325 } 326 } 327 328 - (void)starter:(NSArray*)args { 329 NSAutoreleasePool *pool = [NSAutoreleasePool new]; 330 331 BOOL onMainThread = [[args objectAtIndex:0] boolValue]; 332 BOOL headless = [[args objectAtIndex:1] boolValue]; 333 BOOL verbose = [[args objectAtIndex:2] boolValue]; 334 335 BOOL wasOnMainThread = onMainThread; 336 337 // Add the exception handler of last resort 338 NSSetUncaughtExceptionHandler(AWT_NSUncaughtExceptionHandler); 339 340 // Headless mode trumps either ordinary AWT or SWT-in-AWT mode. Declare us a daemon and return. 341 if (headless) { 342 // Note that we don't install run loop observers in headless mode 343 // because we don't need them (see 7174704) 344 if (!forceEmbeddedMode) { 345 setUpAppKitThreadName(); 346 } 347 [AWTStarter markAppAsDaemon]; 348 return; 349 } 350 351 if (forceEmbeddedMode) { 352 if (verbose) NSLog(@"in SWT or SWT/WebStart mode"); 353 354 // Init a default NSApplication instance instead of the NSApplicationAWT. 355 // Note that [NSApp isRunning] will return YES after that, though 356 // this behavior isn't specified anywhere. We rely on that. 357 NSApplicationLoad(); 358 } 359 360 // This will create a NSApplicationAWT for standalone AWT programs, unless there is 361 // already a NSApplication instance. If there is already a NSApplication instance, 362 // and -[NSApplication isRunning] returns YES, AWT is embedded inside another 363 // AppKit Application. 364 NSApplication *app = [NSApplicationAWT sharedApplication]; 365 isEmbedded = ![NSApp isKindOfClass:[NSApplicationAWT class]]; 366 [ThreadUtilities setAWTEmbedded:isEmbedded]; 367 368 if (!isEmbedded) { 369 // Install run loop observers and set the AppKit Java thread name 370 setUpAWTAppKit(); 371 setUpAppKitThreadName(); 372 } 373 374 // AWT gets to this point BEFORE NSApplicationDidFinishLaunchingNotification is sent. 375 if (![app isRunning]) { 376 if (verbose) AWT_DEBUG_LOG(@"+[AWTStarter startAWT]: ![app isRunning]"); 377 378 // This is where the AWT AppKit thread parks itself to process events. 379 [NSApplicationAWT runAWTLoopWithApp: app]; 380 } else { 381 // We're either embedded, or showing a splash screen 382 if (isEmbedded) { 383 if (verbose) AWT_DEBUG_LOG(@"running embedded"); 384 385 // We don't track if the runloop is busy, so set it free to let AWT finish when it needs 386 setBusy(NO); 387 } else { 388 if (verbose) AWT_DEBUG_LOG(@"running after showing a splash screen"); 389 } 390 391 // Signal so that JNI_OnLoad can proceed. 392 if (!wasOnMainThread) [AWTStarter appKitIsRunning:nil]; 393 394 // Proceed to exit this call as there is no reason to run the NSApplication event loop. 395 } 396 397 [pool drain]; 398 } 399 400 @end 401 402 403 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { 404 BOOL verbose = ShouldPrintVerboseDebugging(); 405 if (verbose) AWT_DEBUG_LOG(@"entered JNI_OnLoad"); 406 407 // Headless: BOTH 408 // Embedded: BOTH 409 // Multiple Calls: NO 410 // Caller: JavaVM classloader 411 412 // Keep a static reference for other archives. 413 OSXAPP_SetJavaVM(vm); 414 415 JNIEnv *env = NULL; 416 417 // Need JNIEnv for JNF_COCOA_ENTER(env); macro below 418 jint status = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4); 419 if (status != JNI_OK || env == NULL) { 420 AWT_DEBUG_LOG(@"Can't get JNIEnv"); 421 return JNI_VERSION_1_4; 422 } 423 424 JNF_COCOA_ENTER(env); 425 426 // Launcher sets this env variable if -XstartOnFirstThread is specified 427 char envVar[80]; 428 snprintf(envVar, sizeof(envVar), "JAVA_STARTED_ON_FIRST_THREAD_%d", getpid()); 429 if (getenv(envVar) != NULL) { 430 forceEmbeddedMode = YES; 431 unsetenv(envVar); 432 } 433 434 if (isSWTInWebStart(env)) { 435 forceEmbeddedMode = YES; 436 } 437 JNIEnv* env = [ThreadUtilities getJNIEnvUncached]; 438 jclass jc_ThreadGroupUtils = (*env)->FindClass(env, "sun/awt/util/ThreadGroupUtils"); 439 jmethodID sjm_getRootThreadGroup = (*env)->GetStaticMethodID(env, jc_ThreadGroupUtils, "getRootThreadGroup", "()Ljava/lang/ThreadGroup;"); 440 jobject rootThreadGroup = (*env)->CallStaticObjectMethod(env, jc_ThreadGroupUtils, sjm_getRootThreadGroup); 441 [ThreadUtilities setAppkitThreadGroup:(*env)->NewGlobalRef(env, rootThreadGroup)]; 442 // The current thread was attached in getJNIEnvUnchached. 443 // Detach it back. It will be reattached later if needed with a proper TG 444 [ThreadUtilities detachCurrentThread]; 445 446 BOOL headless = isHeadless(env); 447 448 // We need to let Foundation know that this is a multithreaded application, if it isn't already. 449 if (![NSThread isMultiThreaded]) { 450 [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]; 451 } 452 453 [AWTStarter start:headless]; 454 455 JNF_COCOA_EXIT(env); 456 457 if (verbose) AWT_DEBUG_LOG(@"exiting JNI_OnLoad"); 458 459 return JNI_VERSION_1_4; 460 }