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